﻿#region Usings

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using RoMBot.Database;
using RoMBot.MemoryLib;
using RoMBot.Properties;
using RoMBot.Waypoints;

#endregion

namespace RoMBot.Classes
{
    public sealed class Player : Pawn
    {
        // The craft numbers corespond with their order in memory
        public const int CraftBlacksmithing = 0;
        public const int CraftCarpentry = 1;
        public const int CraftArmorcrafting = 2;
        public const int CraftTailoring = 3;
        public const int CraftCooking = 4;
        public const int CraftAlchemy = 5;
        public const int CraftMining = 6;
        public const int CraftWoodcutting = 7;
        public const int CraftHerbalism = 8;

        // The 'max' values that each scoring sub-part uses
        private const int ScoreDistance = 60; // closer = more score; actually score will usually be less than half
        private const int ScoreAggressive = 80; // aggressive = score
        private const int ScoreAttacking = 200; // attacking = score
        private const int ScoreHealthPercent = 75; // lower health = more score
        private const float Loopduration = 25f; // The duration we want the MoveTo loop to take
        private const float Successdist = 10f;
        private const int CraftingCountPtr = Addresses.skillsTableBase + 0x50;
        private const int CraftingCountOffset = 0x20;

        private static readonly List<int> ignoreLootableList = new List<int>();
        private static readonly Dictionary<string, int> _killedMobs = new Dictionary<string, int>();
        private static readonly List<KilledMob> _statisticsKilledMobs = new List<KilledMob>();
        private static ObjectsTable _objectsTable;
        private static readonly Stopwatch MovementLoopstart = new Stopwatch();
        private readonly Stopwatch aggroStartTimeSw = new Stopwatch();
        private readonly List<int> disabledSkills = new List<int>();
        private readonly Stopwatch fightTimer = new Stopwatch();
        public PawnClass Class3 = PawnClass.None;
        public EventHandler LevelUp;
        public EventHandler PlayerUpdated;
        private float checkskillsLastCollision = 0f;
        private float checkskillsLastCollisiontryAgain;
        private float checkskillsLastFacetarget;
        private Skill lastSkill;
        private int moveCloseCounter;
        private int tp;
        private Dictionary<int, int> tryiesAtTarget = new Dictionary<int, int>();
        internal Stopwatch petWaitWatch = new Stopwatch();
        private readonly PlayerUserData userData = new PlayerUserData();

        internal Player( bool toCheckPlayerName )
            : base( Addresses.staticbase_char )
        {
        }

        // Default constructor creates a Player for the actual loged in player
        public Player()
            : base( Addresses.staticbase_char )
        {
            this.Skills = new List<Skill>();

            //This allows us to have more instances (for the UI)
            if ( Bot.Player == null )
            {
                Bot.Player = this;
            }

            this.Inventory = new Inventory();
            this.Equipment = new Equipment();
            this.SkillQueue = new Queue<Skill>();
            this.MobIgnoreList = new List<IgnoredMob>();
            this.LastSkill = new Skill();

            this.Profile = new Profile( this.Name );

            this.Update();

            EggPet.UpdateFoods();
            if ( Helpers.IsInGame )
            {
                this.CollisionMsg = Helpers.GetText( "SYS_CASTSPELL_TARGET_COLLISION" );
                Thread.Sleep( 0 );
                this.FaceTargetMsg = Helpers.GetText( "SYS_GAMEMSGEVENT_106" );
                this.UpdateMaxExp();
                this.UpdateExp();
                this.StartExp = this.Exp;

                var cameraAddress = ( int ) Memory.ReadUIntPtr( Addresses.staticbase_char, Addresses.camPtr_offset );
                this.Camera = new Camera( cameraAddress );
            }
            else
            {
                // Dummy camera so we can call SetDirection and such...
                this.Camera = new Camera( 0 );
            }
        }

        public bool HasPetFood
        {
            get { return EggPet.PlayerHasFood; }
        }

        public int StartExp { get; private set; }

        public int MaxExp { get; private set; }

        public int Exp { get; private set; }
        private bool levelUp;
        public int LastExp
        {
            get
            {
                var oldExp = this.Exp;
                this.UpdateExp();
                var currExp = this.Exp;
                if ( oldExp > this.Exp ) // level up?
                {
                    var oldMax = this.MaxExp;
                    this.UpdateMaxExp();
                    if ( oldMax < this.MaxExp )
                    {
                        //its a level up, for sure
                        this.UpdateLevel();
                        levelUp = true;
                        Bot.Printf( Color.Orange, "Level up, we are now lvl {0}!!!", this.Level );
                        //I do this here because we use it in each fight, so its a good place for this.
                        if ( this.LevelUp != null )
                        {
                            this.LevelUp( this, new EventArgs() );
                        }
                        Profile.OnLevelUp();
                        //Reset start exp so we can count minutes to next level
                        StartExp = Exp;
                        oldExp = ( this.MaxExp - oldMax );
                    }
                }
                return Math.Abs( oldExp - currExp );
            }
        }

        private int LastTp
        {
            get
            {
                var currTp = this.tp;
                if ( levelUp )
                {
                    var tpBonus = Memory.ReadRepeatPtr<int>( Addresses.charTpBonusTable_address, ( this.Level - 1 ) * 4 );
                    //We need to substract the bonus from the level up to get correct tp gained per kill.
                    currTp += tpBonus;
                    levelUp = false;
                }
                return this.Tp - currTp;
            }
        }

        public int AggroCount
        {
            get
            {
                var table = new ObjectsTable();
                table.Update( false );
                return table.Count( t => t.Aggressive && t.TargetPtr == this.Address && t.DistanceTo( this ) < ( int ) this.Profile[ "MAX_TARGET_DISTANCE" ] );
            }
        }

        public bool HasAggro
        {
            get { return this.Battling && this.FindEnemy( true, 0, this.EvalTargetDefault ) != null; }
        }

        public int Tp
        {
            get
            {
                this.tp = !this.HasAddress ? 0 : Memory.ReadRepeat<int>( Bot.Instancia.ProcId, Addresses.charClassInfoBase + ( Addresses.charClassInfoSize * ( int ) this.Class1 ) + Addresses.charClassInfoTP_offset );

                return this.tp;
            }
        }

        public bool Running
        {
            get { return !this.Stopped && !this.Paused; }
            set
            {
                this.Stopped = !value;
                this.Paused = !value;
            }
        }

        private DateTime PotionLastManaOnceEmptyTime { get; set; }

        private int PotionManaOnceUsed { get; set; }

        private DateTime PotionLastHpOnceEmptyTime { get; set; }

        private int PotionHpOnceUsed { get; set; }

        private DateTime PotionLastOnceUseTime { get; set; }
        private string CollisionMsg { get; set; }

        private string FaceTargetMsg { get; set; }

        public Queue<Skill> SkillQueue { get; set; }

        public Skill LastSkill
        {
            get { return this.lastSkill ?? ( this.lastSkill = new Skill { Type = SkillType.None } ); }
            set { this.lastSkill = value; }
        }

        public Action Unstick3 { get; set; }
        public Action Unstick6 { get; set; }
        public Action Unstick9 { get; set; }
        private bool OnSkillCastActive { get; set; }

        private bool OnPreSkillCastActive { get; set; }

        private float GlobalCooldown
        {
            get { return Memory.ReadRepeat<int>( Bot.Instancia.ProcId, Addresses.staticCooldownsBase ) / 10f; }
        }

        public bool HasTarget
        {
            get
            {
                var currentTargetPtr = this.TargetPtr;
                var petTargetPtr = this.Pet != null ? this.Pet.TargetPtr : 0;
                if ( currentTargetPtr == 0 && petTargetPtr == 0 )
                {
                    return false;
                }

                if ( this.Target == null || this.Target.Address != currentTargetPtr && this.Target.Address != petTargetPtr )
                {
                    if ( this.targetPtr == 0 && petTargetPtr != 0 ) //Its our pet target
                    {
                        this.Target = new Pawn( petTargetPtr );
                    }
                    else
                    {
                        this.Target = new Pawn( currentTargetPtr );
                    }
                }
                return this.Target != null && this.Target.Exists && this.EvalTargetDefault( this.Target.Address, this.Target );
            }
        }

        public byte HarvestStatus { get; set; }
        public Profile Profile { get; set; }
        public Inventory Inventory { get; set; }

        public float DistanceToTarget
        {
            get
            {
                if ( this.Target != null )
                {
                    return Helpers.Distance( this.X, this.Y, this.Target.X, this.Target.Y );
                }

                return 0;
            }
        }

        public ObjectsTable SurroundingObjectsTable
        {
            get { return _objectsTable; }
            set { _objectsTable = value; }
        }

        public List<Skill> Skills { get; internal set; }

        public DateTime LastUpdate { get; set; }

        public bool Paused { get; set; }

        public bool Stopped { get; set; }

        public byte Stance
        {
            get { return this.HasAddress ? Memory.ReadRepeatPtr<byte>( Addresses.staticbase_char, Addresses.charStance_offset ) : ( byte ) 0; }
        }

        public byte Stance2
        {
            get { return this.HasAddress ? Memory.ReadRepeatPtr<byte>( Addresses.staticbase_char, Addresses.charStance_offset + 2 ) : ( byte ) 0; }
        }

        public float ActualSpeed
        {
            get { return this.HasAddress ? Memory.ReadFloatPtr( Addresses.staticbase_char, Addresses.actualSpeed_offset ) : 0f; }
        }

        public override bool Exists
        {
            get
            {
                var address = ( int ) Memory.ReadRepeatPtr<uint>( Addresses.staticbase_char, Addresses.charPtr_offset );
                if ( address != 0 )
                {
                    var id = ( int ) Memory.ReadRepeat<uint>( Bot.Instance.ProcId, address + Addresses.pawnId_offset );
                    if ( id != this.Id )
                    {
                        this.Id = id;
                    }
                    return id > 0 && id >= 1000 && 1004 >= id;
                }
                return false;
            }
        }

        public bool Moving
        {
            get { return this.ActualSpeed > 0; }
        }

        public int IgnoreTarget { get; set; }

        public WaypointList WaypointList
        {
            get { return Bot.Instance.CurrentWaypointList; }
        }

        public double FightStartTime { get; set; }

        public int FailedCastInARow { get; set; }

        public bool BreakFight { get; set; }

        public bool BreakFromFight { get; set; }

        public double LastHitTime
        {
            get
            {
                var lht = this.HasAddress ? Memory.ReadRepeat<int>( Bot.Instancia.ProcId, Addresses.charLastHitTime ) / 1000d : 0d;
                if ( lht < this.FightStartTime )
                {
                    lht = this.FightStartTime;
                }
                return lht;
            }
        }

        public Stopwatch AggroStartTime
        {
            get { return this.aggroStartTimeSw; }
        }

        public List<int> IgnoreLootableList
        {
            get { return ignoreLootableList; }
        }

        public List<IgnoredMob> MobIgnoreList { get; set; }

        public Waypoint LastPlaceMobIgnored { get; set; }
        public int Psi { get; set; }
        public Equipment Equipment { get; private set; }
        public int Nature { get; private set; }
        public EggPet AssistPet { get; set; }
        public EggPet CraftPet { get; set; }
        public Camera Camera { get; private set; }

        public static Dictionary<string, int> KilledMobs
        {
            get { return _killedMobs; }
        }

        public List<KilledMob> StatisticsKilledMobs
        {
            get { return _statisticsKilledMobs; }
        }

        public override bool Battling
        {
            get
            {
                var battling = this.status == 1;
                if ( !battling && this.Status == 1 )
                {
                    this.AggroStartTime.Restart();
                }
                else
                {
                    this.AggroStartTime.Reset();
                }
                return this.Status == 1;
            }
        }

        public void UpdateExp()
        {
            if ( !this.HasAddress )
            {
                this.Exp = 0;
                return;
            }
            this.Exp = Memory.ReadRepeat<int>( Bot.Instancia.ProcId, Addresses.charClassInfoBase + ( Addresses.charClassInfoSize * ( int ) this.Class1 ) + Addresses.charClassInfoXP_offset );
            this.LastExpUpdateTime = DateTime.Now;
        }

        public void UpdateMaxExp()
        {
            if ( !this.HasAddress )
            {
                this.Exp = 0;
                return;
            }
            var maxExp = Memory.ReadRepeatPtr<int>( Addresses.charMaxExpTable_address, ( this.Level - 1 ) * 4 );
            this.MaxExp = maxExp;
        }

        public bool? CanHarvestWaypoint( Waypoint wp )
        {
            if ( this.DistanceToWaypoint( wp ) > 5000 )
            {
                return null;
            }
            var harvestDist = this.Profile.GetOptionValue<int>( "HARVEST_DISTANCE" );
            var objectList = new ObjectsTable();
            objectList.Update( false, wp, o => o.Type == ObjectType.NpcOrNode && Database.Database.Nodes.ContainsKey( o.Id ) && o.Distance <= harvestDist );
            var resu = ( from o in objectList.Where( o => o != null )
                         where
                                 !(
                                          ( Database.Database.Nodes[ o.Id ].Type == NodeType.Wood && !this.Profile.GetOptionValue<bool>( "HARVEST_WOOD" ) ) ||
                                          ( Database.Database.Nodes[ o.Id ].Type == NodeType.Herb && !this.Profile.GetOptionValue<bool>( "HARVEST_HERB" ) ) ||
                                          ( Database.Database.Nodes[ o.Id ].Type == NodeType.Ore && !this.Profile.GetOptionValue<bool>( "HARVEST_ORE" ) )
                                  )
                         select o ).ToList();
            var found = resu.Any();
            if ( found )
            {
                Bot.Printf( Color.Wheat, "There are {0} harvestables in harvest range. ( {1} )", resu.Count, string.Join( resu.Count == 2 ? " and " : ", ", resu.Select( o => o.Name ) ) );
            }
            else
            {
                Bot.Print( Resources.NoNearByHarvestablesFound, Color.Khaki );
            }
            return found;
        }

        public override void Update()
        {
            try
            {
                var addressChanged = false;
                if ( !this.HasAddress )
                {
                    this.Address = Memory.ReadIntPtr( Addresses.staticbase_char, Addresses.charPtr_offset );
                    addressChanged = true;
                }
                else
                {
                    var tmpaddress = Memory.ReadIntPtr( Addresses.staticbase_char, Addresses.charPtr_offset );
                    if ( tmpaddress > 0 && this.Address != tmpaddress )
                    {
                        this.Address = tmpaddress;
                        addressChanged = true;
                    }
                }
                if ( !this.HasAddress )
                {
                    return;
                }
                var oldClass1 = this.class1;
                var oldClass2 = this.class2;

                base.Update();
                //Hack: i don't want an update function for this, so i force the update by reading the value;
                var updated = false;
                if ( addressChanged | oldClass1 != this.class1 | oldClass2 != this.class2 )
                {
                    this.Inventory.Update();
                    this.Equipment.Update();
                    Bot.Warning( "Address or class changed, updating" );
                    Profile.ProcessSkills();
                    if ( Memory.ReadUInt( Addresses.editBoxHasFocus_address ) == 0 )
                    {
                        Helpers.RoMScript( "z = GetKeyboardFocus(); if z then z:ClearFocus() end" );
                    }
                    addressChanged = false;
                    updated = true;
                }

                this.UpdatePet();
                this.tp = this.Tp;
                this.UpdateTarget();
                this.UpdateMP();
                this.UpdateExp();
                this.UpdateMaxExp();

                if ( this.Address != 0 )
                {
                    this.HarvestStatus = Memory.ReadByte( this.Address + Addresses.pawnHarvesting_offset );
                }
                if ( !updated )
                {
                    return;
                }

                if ( Bot.Player != null && !Bot.Instancia.Stopped )
                {
                    this.SurroundingObjectsTable.Update();
                }

                var reloadAmmo = Profile.GetOptionValue<string>( "RELOAD_AMMUNITION" );
                if ( !string.IsNullOrWhiteSpace( reloadAmmo ) && reloadAmmo != "false" )
                {
                    if ( reloadAmmo == "arrow" )
                    {
                        if ( Class1 == PawnClass.Scout || Class2 == PawnClass.Scout )
                        {

                            Profile.ReloadAmmunitionType = ConsumableType.Arrow;
                        }
                    }
                    if ( reloadAmmo == "thrown" )
                    {
                        if ( Class1 == PawnClass.Rogue || ( Level > 11 && Class2 == PawnClass.Rogue && Level2 > 11 ) )
                        {
                            Profile.ReloadAmmunitionType = ConsumableType.Thrown;
                        }
                    }
                }

                this.LastUpdate = DateTime.Now;
                var pu = this.PlayerUpdated;
                if ( pu != null )
                {
                    pu( this, EventArgs.Empty );
                }

            }
            catch ( Exception e )
            {
                Bot.Error( "there was an error in Player.Update(). " + e.Message );
            }
        }

        public void UpdatePet()
        {
            this.PetPtr = ( int ) Memory.ReadRepeat<uint>( Bot.Instance.ProcId, this.Address + Addresses.pawnPetPtr_offset );
            if ( this.Pet == null || ( this.Pet.Address != this.PetPtr && this.PetPtr != 0 ) )
            {
                this.Pet = new Pet( this.PetPtr );
                petWaitWatch.Restart();
            }
            else
            {
                if ( this.PetPtr != 0 )
                {
                    this.Pet.Update();
                    if ( !string.IsNullOrEmpty( this.Pet.Name ) && this.Pet.Name != UnknowName )
                    {
                        petWaitWatch.Restart();
                    }
                }
                else
                {
                    this.Pet.Name = UnknowName;
                }
            }
        }

        public void FaceDirection( double dir, double? diry = null )
        {
            var vec3 = diry.HasValue ? Math.Sin( ( double ) diry ) : DirectionVector.Y;  // Memory.ReadRepeat<float>( Bot.Instancia.ProcId, this.Address + Addresses.pawnDirYUVec_offset );
            var hypotenuse = Math.Pow( 1 - Math.Pow( vec3, 2 ), .5 );
            var vec1 = Math.Cos( dir ) * hypotenuse;
            var vec2 = Math.Sin( dir ) * hypotenuse;

            this.Direction = ( float ) Math.Atan2( vec2, vec1 );
            //math.atan2(Vec3, (Vec1^2 + Vec2^2)^.5 );
            this.DirectionY = ( float ) Math.Atan2( vec3, Math.Pow( ( Math.Pow( vec1, 2 ) + Math.Pow( vec2, 2 ) ), .5 ) );

            var tmpMountAddress = Memory.ReadRepeat<uint>( Bot.Instancia.ProcId, this.Address + Addresses.charPtrMounted_offset );
            if ( this.Mounted && tmpMountAddress != 0 )
            {
                Memory.WriteFloat( ( int ) tmpMountAddress + Addresses.pawnDirXUVec_offset, ( float ) vec1 );
                Memory.WriteFloat( ( int ) tmpMountAddress + Addresses.pawnDirZUVec_offset, ( float ) vec2 );
                Memory.WriteFloat( ( int ) tmpMountAddress + Addresses.pawnDirYUVec_offset, ( float ) vec3 );
            }
            else
            {
                Memory.WriteFloat( this.Address + Addresses.pawnDirXUVec_offset, ( float ) vec1 );
                Memory.WriteFloat( this.Address + Addresses.pawnDirZUVec_offset, ( float ) vec2 );
                Memory.WriteFloat( this.Address + Addresses.pawnDirYUVec_offset, ( float ) vec3 );
            }
        }

        public void ClearTarget()
        {
            Bot.Print( Resources.ClearingTarget, Color.Green );
            this.Target = null;
            this.TargetPtr = 0;
            this.LastTargetPtr = 0;
            this.tryiesAtTarget.Clear();
            Memory.WriteInt( this.Address + Addresses.pawnTargetPtr_offset, 0 );
            Helpers.RoMScript( "TargetFrame:Hide()" );
        }

        public void SelectTarget( bool friendly = false )
        {
            if ( this.targetPtr != 0 )
            {
                var tmpPtr = this.targetPtr;
                if ( tmpPtr == this.TargetPtr )
                {
                    if ( this.Target == null )
                    {
                        this.Target = new Pawn( this.targetPtr );
                    }
                    //Already have target
                    return;
                }
            }
            this.TargetPtr = 0;
            this.Target = null;
            Pawn pawn1 = null;
            double num1 = 0;
            var num2 = 60;
            var num3 = 80;
            var num4 = 100;
            var num5 = 75;
            Monitor.Enter( this.SurroundingObjectsTable );
            this.SurroundingObjectsTable.Update();
            foreach ( var gameObject in this.SurroundingObjectsTable.Where( o => friendly ? ( o.Type != ObjectType.Monster ) : o.Type == ObjectType.Monster && ( double ) o.Distance <= ( double ) this.Profile.GetOptionValue<int>( "MAX_TARGET_DISTANCE" ) ) )
            {
                var optionValue = this.Profile.GetOptionValue<int>( "MAX_TARGET_DIST" );
                double num6 = 0;
                var pawn2 = new Pawn( gameObject.Address );
                var num7 = num6 + ( optionValue - ( double ) pawn2.Distance ) / optionValue * num2 + ( pawn2.MaxHP - pawn2.HP ) / ( pawn2.MaxHP > 0 ? pawn2.MaxHP : 1 ) * num5;
                if ( pawn2.TargetPtr == this.Address )
                {
                    num7 += num4;
                }
                if ( pawn2.Aggressive )
                {
                    num7 += num3;
                }
                if ( pawn1 == null )
                {
                    pawn1 = pawn2;
                    num1 = num7;
                }
                else if ( num7 > num1 )
                {
                    pawn1 = pawn2;
                    num1 = num7;
                }
            }
            Monitor.Exit( this.SurroundingObjectsTable );

            this.Target = pawn1;
            if ( this.Target == null )
            {
                return;
            }
            this.TargetPtr = this.Target.Address;
        }

        public Pawn FindEnemy( bool aggroOnly = false, int id = 0, Func<int, Pawn, bool> evalFunc = null, int ignore = 0 )
        {
            // If aggroonly, check to see if you have already started attacking current target
            if ( aggroOnly )
            {
                if ( this.HasTarget )
                {
                    if ( this.Target == null )
                    {
                        this.Target = new Pawn( this.TargetPtr );
                    }
                    if ( this.Target.TargetPtr == this.Address ||
                         this.Target.TargetIsFriend() ||
                         this.Target.LastDamage > 0 )
                    {
                        return this.Target;
                    }
                }
            }

            //Pawn bestEnemy = null;
            var bestScore = 0f;
            //Pawn obj = null;

            if ( evalFunc == null )
            {
                evalFunc = this.EvalTargetDefault;
            }

            var maxTargetDist = this.Profile.GetOptionValue<int>( "MAX_TARGET_DISTANCE" );

            var objectList = new ObjectsTable();
            objectList.Update( false, filterFn: o =>
                                                o != null &&
                                                o.Type == ObjectType.Monster &&
                                                o.Attackable &&
                                                o.Distance < maxTargetDist &&
                                                o.Address != ignore &&
                                                !( id != o.Id && id != 0 ) &&
                                                evalFunc( o.Address, null ) &&
                                                ( !aggroOnly || ( o.TargetPtr == this.Address | o.TargetPtr == this.PetPtr ) ) );

            var objects = objectList.ToList();
            var bestEnemyAdress = 0;
            foreach ( var obj in objects )
            {
                //var pawn = new Pawn( obj.Address );
                var currentScore = 0f;
                currentScore += ( ( maxTargetDist - obj.Distance ) / maxTargetDist * ScoreDistance );
                var mhp = obj.maxHP; //Single read
                currentScore += ( ( mhp - obj.hp ) / mhp * ScoreHealthPercent );

                if ( obj.TargetPtr == this.Address || obj.TargetIsFriend( aggroOnly ) )
                {
                    currentScore += ScoreAttacking;
                }

                if ( obj.Aggressive )
                {
                    currentScore += ScoreAggressive;
                }

                if ( bestEnemyAdress == 0 )
                {
                    bestEnemyAdress = obj.Address;
                    //bestEnemy = pawn;
                    bestScore = currentScore;
                }
                else if ( currentScore > bestScore )
                {
                    bestEnemyAdress = obj.Address;
                    //bestEnemy = pawn;
                    bestScore = currentScore;
                }
            }

            return bestEnemyAdress > 0 ? new Pawn( bestEnemyAdress ) : null;
        }

        public GameObject FindNearestNameOrId( dynamic objtable, int ignore = 0, Func<int, GameObject, bool> evalFunc = null )
        {
            if ( !objtable.GetType().IsArray )
            {
                objtable = new[] { objtable };
            }

            if ( null == evalFunc )
            {
                evalFunc = ( a, g ) => true;
            }
            GameObject closestObject = null;
            //var obj = null;
            var objectList = new ObjectsTable();
            objectList.Update();

            foreach ( var obj in objectList )
            {
                if ( obj == null )
                {
                    continue;
                }
                foreach ( var objnameorid in objtable )
                {
                    var id = ( objnameorid is int ) ? ( int ) objnameorid : -1;
                    var name = id < 0 ? objnameorid.ToString().ToLowerInvariant() : string.Empty;
                    if ( objnameorid == null || obj.Address == ignore || obj.Address == this.Address ||
                         ( ( id > 0 && obj.Id != id ) ) ||
                         ( ( !string.IsNullOrEmpty( name ) && !obj.Name.ToLower().Contains( name ) ) ) ||
                         !evalFunc( obj.Address, obj ) )
                    {
                        continue;
                    }
                    var dist = Helpers.Distance( this.X, this.Z, this.Y, obj.X, obj.Z, obj.Y );
                    if ( closestObject == null )
                    {
                        closestObject = obj;
                    }
                    else
                    {
                        if ( dist < Helpers.Distance( this.X, this.Z, this.Y, closestObject.X, closestObject.Z, closestObject.Y ) )
                        {
                            // this node is closer
                            closestObject = obj;
                        }
                    }
                }
            }

            return closestObject;
        }

        private bool CanHarvest( GameObject obj )
        {
            if ( obj == null )
            {
                return false;
            }
            if ( !Database.Database.Nodes.ContainsKey( obj.Id ) )
            {
                return true;
            }

            var harvestLevel = Database.Database.Nodes[ obj.Id ].Level;
            var harvestType = Database.Database.Nodes[ obj.Id ].Type;
            var craftLevel = 0f;
            switch ( harvestType )
            {
                case NodeType.Ore:
                    craftLevel = GetCraftLevel( CraftMining );
                    break;
                case NodeType.Wood:
                    craftLevel = GetCraftLevel( CraftWoodcutting );
                    break;
                case NodeType.Herb:
                    craftLevel = GetCraftLevel( CraftHerbalism );
                    break;
            }

            return craftLevel >= harvestLevel;
        }

        public bool Harvest( int id = 0, bool secondTry = false )
        {
            var nodeStillFound = new Func<GameObject, bool>(
                    node =>
                    {
                        var objectList = new ObjectsTable();
                        objectList.Update();
                        return objectList.Any( o => o.Address == node.Address );
                    } );

            ReleaseKeys();
            // Make sure we come to a stop before attempting to harvest.
            this.WaitTillStopMoving();
            var lastHarvestedNodeAddr = 0;
            while ( this.Running )
            {
                var closestHarvestable = this.FindClosestHarvestable( id, lastHarvestedNodeAddr );
                if ( closestHarvestable == null )
                {
                    Bot.Print( lastHarvestedNodeAddr == 0 ? Resources.NoNearByHarvestablesFound : "Nothing else to harvest here." );
                    return false;
                }

                if ( this.Profile.HasOnHarvest )
                {
                    if ( !this.Profile.OnHarvest( closestHarvestable ) )
                    {
                        return false;
                    }
                }

                // Check harvest skill level.
                if ( !this.CanHarvest( closestHarvestable ) )
                {
                    Bot.Print( Resources.HarvestSkillLvlToLow );
                    return false;
                }

                Bot.Printf( Color.Yellow, Resources.WeFoundAndWillHarvest, closestHarvestable.Name );

                if ( Helpers.Distance( this.X, this.Z, this.Y, closestHarvestable.X, closestHarvestable.Z, closestHarvestable.Y ) > 80 )
                {
                    if ( this.MoveInRange( new Waypoint( closestHarvestable.X, closestHarvestable.Z ), 39, true ).Item1 )
                    {
                        this.WaitTillStopMoving();
                    }
                }

                if ( this.Running && nodeStillFound( closestHarvestable ) )
                {
                    this.SetTarget( closestHarvestable.Address );
                    Thread.CurrentThread.Join( 100 );
                    this.WaitTillStopMoving();
                    this.Attack();
                    Thread.CurrentThread.Join( 100 );
                }
                else
                {
                    return false; // Node disappeared for whatever reason+.
                }

                if ( id != 0 && !Database.Database.Nodes.ContainsKey( closestHarvestable.Id ) )
                { // The rest is not needed if not resource node
                    return true;
                }

                Thread.CurrentThread.Join( 500 );

                //this.UpdateBattling();
                var timeStart = Stopwatch.StartNew();
                var skip = false;
                while ( !this.Harvesting && this.Running )
                {
                    // Wait to start harvesting
                    Thread.CurrentThread.Join( 100 );
                    //this.UpdateBattling();
                    if ( this.HasAggro /*this.Battling && this.FindEnemy( true, 0, this.EvalTargetDefault ) != null*/ )
                    {
                        Bot.Print( Resources.HarvestingCancelledDueToAgressiveEnemies );
                        skip = true;
                        break;
                    }

                    if ( timeStart.ElapsedMilliseconds > 3000 )
                    {
                        // Maybe the command didn't go through. Try once more.
                        this.Attack();
                        Thread.CurrentThread.Join( 500 );
                        break;
                    }
                }
                //this.UpdateBattling();
                timeStart.Restart();
                while ( this.Running && this.Harvesting && skip == false )
                {
                    Thread.CurrentThread.Join( 100 );
                    //this.UpdateBattling();
                    if ( this.HasAggro /*this.Battling && this.FindEnemy( true, 0, this.EvalTargetDefault ) != null */ )
                    {
                        Bot.Print( Resources.HarvestingCancelledDueToAgressiveEnemies );
                        break;
                    }

                    if ( !nodeStillFound( closestHarvestable ) || this.targetPtr != closestHarvestable.Address )
                    {
                        break;
                    }

                    if ( timeStart.ElapsedMilliseconds > 45000 )
                    {
                        // Taking too long. Drop out.
                        Bot.Print( Resources.StopHarvestingTakingTooLong );
                        break;
                    }
                }

                //this.UpdateBattling();
                var interrupted = false;
                while ( this.Battling && this.Running )
                {
                    if ( this.SetTarget( this.FindEnemy( true, 0, this.EvalTargetDefault ) ) )
                    {
                        interrupted = true;
                        this.Fight();
                        Thread.CurrentThread.Join( 200 + Helpers.Ping );
                    }
                    else
                    {
                        break;
                    }
                }

                if ( !interrupted )
                {
                    lastHarvestedNodeAddr = closestHarvestable.Address;
                }
            }
            return false;
        }

        internal void ResetAllTasks()
        {
            this.Stopped = false;
            this.Paused = false;
            ReleaseKeys();
        }

        private GameObject FindClosestHarvestable( int id, int ignore )
        {
            GameObject closestHarvestable;
            var objectList = new ObjectsTable();
            objectList.Update();
            var resu = ( from o in objectList.Where( o => Database.Database.Nodes.ContainsKey( o.Id ) )
                         let dist = this.DistanceTo( o )
                         where o != null &&
                               o.Type == ObjectType.NpcOrNode &&
                               o.Address != ignore &&
                               ( id == o.Id || ( id == 0 && Database.Database.Nodes.ContainsKey( o.Id ) ) ) &&
                               !(
                                        ( Database.Database.Nodes[ o.Id ].Type == NodeType.Wood && !this.Profile.GetOptionValue<bool>( "HARVEST_WOOD" ) ) ||
                                        ( Database.Database.Nodes[ o.Id ].Type == NodeType.Herb && !this.Profile.GetOptionValue<bool>( "HARVEST_HERB" ) ) ||
                                        ( Database.Database.Nodes[ o.Id ].Type == NodeType.Ore && !this.Profile.GetOptionValue<bool>( "HARVEST_ORE" ) )
                                ) &&
                               dist <= this.Profile.GetOptionValue<int>( "HARVEST_DISTANCE" )
                         orderby dist
                         select o ).FirstOrDefault();
            return resu;
            foreach ( var obj in objectList )
            {
                if ( obj == null )
                {
                    continue;
                }
                if ( obj.Type == ObjectType.NpcOrNode && obj.Address != ignore &&
                     ( id == obj.Id || ( id == 0 && Database.Database.Nodes.ContainsKey( obj.Id ) ) ) )
                {
                    var harvestThis = true;
                    if ( Database.Database.Nodes[ obj.Id ] != null )
                    {
                        var harvestType = Database.Database.Nodes[ obj.Id ].Type;
                        if ( harvestType == NodeType.Wood && !this.Profile.GetOptionValue<bool>( "HARVEST_WOOD" ) )
                        {
                            harvestThis = false;
                        }
                        else if ( harvestType == NodeType.Herb && !this.Profile.GetOptionValue<bool>( "HARVEST_HERB" ) )
                        {
                            harvestThis = false;
                        }
                        else if ( harvestType == NodeType.Ore && !this.Profile.GetOptionValue<bool>( "HARVEST_ORE" ) )
                        {
                            harvestThis = false;
                        }
                    }

                    if ( harvestThis )
                    {
                        if ( closestHarvestable == null )
                        {
                            if ( this.DistanceTo( obj ) < this.Profile.GetOptionValue<int>( "HARVEST_DISTANCE" ) )
                            {
                                closestHarvestable = obj;
                            }
                        }
                        else
                        {
                            if ( this.DistanceTo( obj ) < this.DistanceTo( closestHarvestable ) )
                            {
                                // this node is closer
                                closestHarvestable = obj;
                            }
                        }
                    }
                }
            }

            return closestHarvestable;
        }

        private Tuple<bool, WpFailReason> MoveInRange( Waypoint waypoint, int range = 0, bool ignoreCycleTargets = false )
        {
            return this.MoveTo( waypoint, ignoreCycleTargets, false, range );
        }

        private Tuple<bool, WpFailReason> MoveInRange( GameObject pawn, int range = 0, bool ignoreCycleTargets = false )
        {
            return this.MoveTo( pawn, ignoreCycleTargets, false, range );
        }

        private static float GetCraftLevel( int craftType )
        {
            return Memory.ReadFloat( Addresses.playerCraftLevelBase + Addresses.playerCraftLevel_offset + craftType * 4 );
        }

        public Tuple<bool, WpFailReason> MoveTo( GameObject pawn, bool ignoreCycleTargets = false, bool dontStopAtEnd = false, int range = 0 )
        {
            return this.MoveTo( new Waypoint( pawn.X, pawn.Z ), ignoreCycleTargets, dontStopAtEnd, range );
        }

        public Tuple<bool, WpFailReason> MoveTo( Waypoint waypoint, bool ignoreCycleTargets = false, bool dontStopAtEnd = false, int range = 0 )
        {
            if ( waypoint.Type == WaypointType.Travel || waypoint.Type == WaypointType.Run || waypoint.Type == WaypointType.Harvest )
            {
                ignoreCycleTargets = true;
                // don't target mobs 
            }

            // Make sure we don't have a garbage (dead) target
            if ( this.TargetPtr != 0 )
            {
                var target = new Pawn( this.TargetPtr );
                if ( target.Exists )
                { // Target exists
                    if ( target.HP <= 1 )
                    {
                        this.ClearTarget();
                    }
                }
            }

            if ( !this.FixRunningDirection( waypoint ) )
            {
                return Tuple.Create( false, WpFailReason.WfNone );
            }

            // look for a target before start movig
            if ( !this.Fighting && !ignoreCycleTargets )
            {
                if ( this.SetTarget( this.FindEnemy( false, 0, this.EvalTargetDefault, this.IgnoreTarget ) ) )
                { // find a new target
                    Bot.Print( Resources.StopingWaypointTargetAquiredBeforeMoving, Color.DarkTurquoise );
                    return Tuple.Create( false, WpFailReason.WfTarget );
                }
            }

            var success = true;
            var reason = WpFailReason.WfNone;
            var dist = this.DistanceToWaypoint( waypoint );
            var lastDist = dist;
            var lastpos = new Waypoint { X = this.X, Z = this.Z, Y = this.Y };
            this.LastDistImprove = DateTime.Now; // global, because we reset it whil skill use

            var turning = false;
            Task rotationFix = null;
            var quickTurn = this.Profile.GetOptionValue<bool>( "QUICK_TURN" );

            do
            {
                MovementLoopstart.Restart();

                if ( !this.Running )
                {
                    ReleaseKeys();
                    success = false;
                    break;
                }
                this.CheckAddress();

                if ( this.HP < 1 || !this.Alive )
                {
                    success = false;
                    break;
                }

                // stop moving if aggro, bot will stand and wait until to get the target from the client
                // only if not in the fight stuff coding (means this.Fighting == false )
                if ( this.Battling && // we have aggro
                     !this.Fighting && // we are not coming from the fight routines (bec. as melee we should move in fight)
                     ( waypoint.Type != WaypointType.Travel ) ) // only stop if not waypoint type TRAVEL
                {
                    if ( this.SetTarget( this.FindEnemy( true, 0, this.EvalTargetDefault ) ) )
                    {
                        Keyboard.ReleaseKey( Profile.HotKeys.MOVE_FORWARD.Key );
                        Keyboard.ReleaseKey( Profile.HotKeys.ROTATE_LEFT.Key );
                        Keyboard.ReleaseKey( Profile.HotKeys.ROTATE_RIGHT.Key );
                        success = false;
                        reason = WpFailReason.WfCombat;
                        break;
                    }
                }

                // look for a new target while moving
                if ( !ignoreCycleTargets && !this.Fighting && !turning )
                {
                    if ( this.SetTarget( this.FindEnemy( false, 0, this.EvalTargetDefault, this.IgnoreTarget ) ) )
                    {
                        Bot.Print( Resources.StoppingWaypointTargetAquired, Color.DarkTurquoise );
                        Keyboard.ReleaseKey( Profile.HotKeys.MOVE_FORWARD.Key );
                        Keyboard.ReleaseKey( Profile.HotKeys.ROTATE_LEFT.Key );
                        Keyboard.ReleaseKey( Profile.HotKeys.ROTATE_RIGHT.Key );
                        success = false;
                        reason = WpFailReason.WfTarget;
                        break;
                    }
                }

                dist = this.DistanceToWaypoint( waypoint );
                // We're still making progress
                if ( dist < lastDist )
                {
                    this.LastDistImprove = DateTime.Now;
                    lastDist = dist;
                    lastpos = new Waypoint { X = this.X, Z = this.Z, Y = this.Y };
                }
                else if ( dist > lastDist + 40 )
                {
                    // Check if pulled back before last waypoint
                    var lastwp = this.WaypointList.GetNextWayPoint( -1 );
                    if ( ( lastwp.X != 0 || lastwp.Z != 0 || lastwp.Y != null ) && Helpers.Distance( this, lastpos ) >
                         Helpers.Distance( lastwp, lastpos ) )
                    {
                        Bot.Print( Resources.WpPulledBackResetting );
                        success = false;
                        reason = WpFailReason.WfPullback;
                    }
                    else
                    {
                        // Make sure we didn't pass it up
                        Bot.Print( Resources.WpPassedResetting );
                        success = false;
                        reason = WpFailReason.WfDist;
                    }
                    break;
                }

                if ( ( DateTime.Now - this.LastDistImprove ).Seconds > 3 )
                {
                    // We haven't improved for 3 seconds, assume stuck
                    success = false;
                    reason = WpFailReason.WfStuck;
                    break;
                }

                // This is so we can use MoveTo inside user functions in the console and not cast skills or use potions
                // If you still want the bot to care of those things but not use WaypointList, set the bot to assist mode
                // Bot.AssistMode = true
                // And then start it from your function
                // Bear in mind that, if you stop it inside your function, the bot will stop completelly, the thread destroyed
                // and your function aborted, so, don't stop it until you end.
                if ( !Bot.Instancia.Stopped && this.Running )
                {
                    // while moving without target: check potions / friendly skills
                    if ( !this.Fighting && !this.Mounted && ( this.CheckPotions() | this.CheckSkills( true ) ) )
                    { // only cast friendly spells to ourselfe
                        // If we used a potion or a skill, reset our last dist improvement
                        // to prevent unsticking
                        this.LastDistImprove = DateTime.Now;

                        // Wait for casting to finish if still casting last skill
                        while ( this.Casting )
                        {
                            Thread.CurrentThread.Join( 10 );
                        }
                    }
                }

                // Check if within range if range specified
                if ( range > 0 && range >= ( int ) dist )
                {
                    // within range
                    success = true;
                    reason = WpFailReason.WfNone;
                    break;
                }

                // Check if past waypoint
                if ( this.PassedPoint( lastpos, waypoint ) )
                {
                    // waypoint reached
                    break;
                }

                // Check if close to waypoint.
                if ( dist < Successdist )
                {
                    break;
                }

                var yangle = YAngleWaypoint( waypoint );
                var angle = Math.Atan2( waypoint.Z - this.Z, waypoint.X - this.X );
                //if ( waypoint.Y != null )
                //{
                //    yangle = Math.Atan2( ( double ) ( waypoint.Y - this.Y ), Math.Pow( Math.Pow( ( waypoint.X - this.X ), 2 ) + Math.Pow( ( waypoint.Z - this.Z ), 2 ), .5 ) );
                //}

                //this.UpdateDirection();
                var angleDif = Helpers.AngleDifference( angle, this.Direction );

                Helpers.Yrest( 1 );

                if ( quickTurn && angleDif > Helpers.Rad( 1d ) )
                {
                    this.FaceDirection( angle, yangle );
                    this.Camera.SetRotation( ( float ) angle );
                    angleDif = Helpers.AngleDifference( angle, this.Direction );
                    Keyboard.HoldKey( Profile.HotKeys.MOVE_FORWARD.Key );
                }
                else
                {
                    this.FaceDirection( this.Direction, yangle ); // change only 'Y' angle with 'faceDirection'.

                    if ( angleDif > Helpers.Rad( 15 ) )
                    {
                        if ( Helpers.AngleDifference( angle, this.Direction + 0.01 ) < angleDif )
                        {
                            Keyboard.ReleaseKey( Profile.HotKeys.ROTATE_RIGHT.Key );
                            Keyboard.HoldKey( Profile.HotKeys.ROTATE_LEFT.Key );
                            Thread.CurrentThread.Join( 100 );
                        }
                        else
                        {
                            Keyboard.ReleaseKey( Profile.HotKeys.ROTATE_LEFT.Key );
                            Keyboard.HoldKey( Profile.HotKeys.ROTATE_RIGHT.Key );
                            Thread.CurrentThread.Join( 100 );
                        }
                        turning = true;
                    }
                    else if ( angleDif > Helpers.Rad( 1 ) )
                    {
                        if ( this.Profile.GetOptionValue<bool>( "QUICK_TURN" ) )
                        {
                            this.Camera.SetRotation( ( float ) angle );
                        }

                        this.FaceDirection( angle, yangle );
                        Keyboard.ReleaseKey( Profile.HotKeys.ROTATE_LEFT.Key );
                        Keyboard.ReleaseKey( Profile.HotKeys.ROTATE_RIGHT.Key );
                        Keyboard.HoldKey( Profile.HotKeys.MOVE_FORWARD.Key );
                        turning = false;
                    }
                    else
                    {
                        Keyboard.HoldKey( Profile.HotKeys.MOVE_FORWARD.Key );
                    }
                    Thread.CurrentThread.Join( 10 );
                }
                var pausetime = Loopduration - MovementLoopstart.ElapsedMilliseconds; // minus the time already elapsed.
                if ( pausetime < 1 )
                {
                    pausetime = 1;
                }
                Thread.CurrentThread.Join( TimeSpan.FromMilliseconds( pausetime ) );
                waypoint.Update();
                //if ( this.Battling && this.HP <= this.maxHP / 4 )
                //{
                //    success = false;
                //    reason = WpFailReason.WfCombat;
                //    break;
                //}
            } while ( this.Running && dist > Successdist );

            //if ( rotationFix != null )
            //{
            //    rotationFix.Wait( 1 );
            //}

            if ( this.Profile.GetOptionValue<bool>( "WP_NO_STOP" ) )
            {
                if ( !dontStopAtEnd || !this.Profile.GetOptionValue<bool>( "QUICK_TURN" ) )
                {
                    Keyboard.ReleaseKey( Profile.HotKeys.MOVE_FORWARD.Key );
                }
            }
            else
            {
                Keyboard.ReleaseKey( Profile.HotKeys.MOVE_FORWARD.Key );
            }

            Keyboard.ReleaseKey( Profile.HotKeys.ROTATE_LEFT.Key );
            Keyboard.ReleaseKey( Profile.HotKeys.ROTATE_RIGHT.Key );
            if ( Bot.DebugWaypoints && !success )
            {
                Bot.Printf( "Movement failed, reason: {0}", reason );
            }

            return Tuple.Create( success, reason );
        }

        public void LevelUpSkills1To10()
        {
            var skillLevelupMap = new[]
                                  {
                                          new[]
                                          {
                                                  new {aslevel = 1, skillname = "WARRIOR_SLASH"},
                                                  new {aslevel = 2, skillname = "WARRIOR_OPEN_FLANK"},
                                                  new {aslevel = 2, skillname = "WARRIOR_PROBING_ATTACK"},
                                                  new {aslevel = 4, skillname = "WARRIOR_ENRAGED"},
                                                  new {aslevel = 6, skillname = "WARRIOR_THUNDER"}
                                          },
                                          new[]
                                          {
                                                  new {aslevel = 1, skillname = "SCOUT_SHOT"},
                                                  new {aslevel = 2, skillname = "SCOUT_WIND_ARROWS"},
                                                  new {aslevel = 4, skillname = "SCOUT_VAMPIRE_ARROWS"},
                                                  new {aslevel = 6, skillname = "490445"} // passive Ranged Weapon Mastery
                                          },
                                          new[]
                                          {
                                                  new {aslevel = 1, skillname = "ROGUE_SHADOWSTAB"},
                                                  new {aslevel = 2, skillname = "ROGUE_LOW_BLOW"},
                                                  new {aslevel = 6, skillname = "ROGUE_WOUND_ATTACK"},
                                                  new {aslevel = 8, skillname = "ROGUE_BLIND_STAB"}
                                          },
                                          new[]
                                          {
                                                  new {aslevel = 1, skillname = "MAGE_FLAME"},
                                                  new {aslevel = 1, skillname = "MAGE_PLASMA_ARROW"},
                                                  new {aslevel = 4, skillname = "MAGE_FIREBALL"}
                                          },
                                          new[]
                                          {
                                                  new {aslevel = 1, skillname = "PRIEST_RISING_TIDE"},
                                                  new {aslevel = 1, skillname = "PRIEST_URGENT_HEAL"},
//								 new { aslevel = 2, skillname="PRIEST_WAVE_ARMOR" },	// needs to much mana
                                                  new {aslevel = 4, skillname = "PRIEST_REGENERATE"},
                                                  new {aslevel = 8, skillname = "PRIEST_HOLY_AURA"}
                                          },
                                          new[]
                                          {
                                                  new {aslevel = 1, skillname = "KNIGHT_PUNISHMENT"},
                                                  new {aslevel = 1, skillname = "KNIGHT_HOLY_STRIKE"}
                                          },
                                          new[]
                                          {
                                                  new {aslevel = 1, skillname = "WARDEN_CHARGED_CHOP"},
                                                  new {aslevel = 1, skillname = "WARDEN_ENERGY_ABSORB"},
                                                  new {aslevel = 2, skillname = "WARDEN_THORNY_VINES"},
                                                  new {aslevel = 4, skillname = "WARDEN_BRIAR_SHIELD"},
                                                  new {aslevel = 8, skillname = "WARDEN_POWER_OF_THE_WOOD_SPIRIT"}
                                          },
                                          new[]
                                          {
                                                  new {aslevel = 1, skillname = "DRUID_RECOVER"},
                                                  new {aslevel = 1, skillname = "DRUID_EARTH_ARROW"},
                                                  new {aslevel = 2, skillname = "DRUID_BRIAR_ENTWINEMENT"},
                                                  new {aslevel = 6, skillname = "DRUID_RESTORE_LIFE"}
                                          },
                                          new[]
                                          {
                                                  new {aslevel = 1, skillname = "WARLOCK_PSYCHIC_ARROWS"},
                                                  new {aslevel = 4, skillname = "WARLOCK_WARP_CHARGE"},
                                                  new {aslevel = 6, skillname = "WARLOCK_PERCEPTION_EXTRACTION"},
                                                  new {aslevel = 8, skillname = "497961"} // passive Pure Soul
                                          },
                                          new[]
                                          {
                                                  new {aslevel = 1, skillname = "CHAMPION_ELECTROCUTION"},
                                                  new {aslevel = 1, skillname = "CHAMPION_HEAVY_BASH"},
                                                  new {aslevel = 6, skillname = "CHAMPION_ENERGY_INFLUX_STRIKE"},
                                                  new {aslevel = 8, skillname = "498525"} // passive Finishing Hammer
                                          }
                                  };

            var levelUpTable = skillLevelupMap[ ( int ) Class1 - 1 ];
            var lvl = this.Level;
            foreach ( var skill in levelUpTable )
            {
                if ( skill.aslevel <= lvl )
                {
                    var dbSkill = Bot.Instance.Skills.ContainsKey( skill.skillname ) ? Bot.Instance.Skills[ skill.skillname ] : null;
                    if ( dbSkill != null )
                    {
                        if ( lvl == skill.aslevel )
                        { // maxlevel the new skill
                            LevelUpSkill( skill.skillname, skill.aslevel );
                        }
                        else if ( lvl == 2 && skill.aslevel == 1 )
                        { // 2x aft level 2
                            LevelUpSkill( skill.skillname, 2 );
                        }
                        else if ( lvl > skill.aslevel )
                        { // levelup 1 level
                            LevelUpSkill( skill.skillname );
                        }
                    }

                    if ( !Profile.Skills.ContainsKey( skill.skillname ) )//Lets add it to profile
                    {
                        dbSkill = Bot.Instance.Skills.ContainsKey( skill.skillname ) ? Bot.Instance.Skills[ skill.skillname ] : null;
                        if ( dbSkill != null )
                        {
                            Profile.Skills.Add( skill.skillname, new Skill( skill.skillname )
                                                                 {
                                                                     HotKey = Key.MACRO,
                                                                     Level = 1
                                                                 } );

                            Bot.Printf( Color.LightBlue, "We learned skill '{0}' and will use it.", skill.skillname );	// Open/eqipt item
                            Database.Skills.ProcessPlayerSkills();
                        }
                    }
                }
            }
        }

        public void LevelUpSkill( string skillId, int times = 1 )
        {
            var dbSkill = Bot.Instance.Skills.ContainsKey( skillId ) ? Bot.Instance.Skills[ skillId ] : null;
            if ( dbSkill == null )
            {
                Bot.Error( "Bar argument passed to LevelUpSkill: " + skillId );
                return;
            }

            if ( dbSkill.Aslevel > Level )
            {
                Bot.Printf( Color.Yellow, "You need to be at least: {0} to use the skill: {1}", dbSkill.Aslevel, dbSkill.Name );
                return;
            }

            if ( dbSkill.SkillNum == 0 || dbSkill.SkillTab == 0 )
            {
                Bot.Printf( Color.Yellow, "Missing level up parameters in skill: {0}", dbSkill.Name );
                return;
            }

            var leveled = false;
            for ( var i = 0; i < times; i++ )
            {
                Helpers.Yrest( 600 );
                //local name, _, icon, _, rank, type, upgradeCost, isSkillable, isAvailable = RoMScript("GetSkillDetail("..skill_from_db.skilltab..","..skill_from_db.skillnum..")")
                var skillDetails = Helpers.RoMScript<List<object>>( "GetSkillDetail(" + dbSkill.SkillTab + "," + dbSkill.SkillNum + ")" );
                if ( skillDetails != null )
                {
                    var upgradeCost = ( int ) skillDetails[ 6 ];
                    if ( this.tp >= upgradeCost )
                    {
                        Helpers.RoMScript( "SetSpellPoint(" + dbSkill.SkillTab + "," + dbSkill.SkillNum + ");" );
                        leveled = true;
                    }
                    else
                    {
                        break;
                    }
                }
            }

            if ( leveled )
            {
                Profile.ProcessSkills();
            }
        }

        private float YAngleWaypoint( Waypoint waypoint )
        {
            return ( float ) ( waypoint.Y.HasValue ? Math.Atan2( ( double ) ( waypoint.Y - this.Y ), Math.Pow( Math.Pow( ( waypoint.X - this.X ), 2 ) + Math.Pow( ( waypoint.Z - this.Z ), 2 ), .5 ) ) : 0f );
        }

        private void FixRunningDirectionWhileRunning( Waypoint waypoint )
        {
            try
            {
                while ( this.Running )
                {
                    var yangle = 0d;
                    var angle = Math.Atan2( waypoint.Z - this.Z, waypoint.X - this.X );
                    //bool turning;
                    if ( waypoint.Y != null )
                    {
                        yangle = Math.Atan2( ( double ) ( waypoint.Y - this.Y ), Math.Pow( Math.Pow( ( waypoint.X - this.X ), 2 ) + Math.Pow( ( waypoint.Z - this.Z ), 2 ), .5 ) );
                    }

                    var angleDif = Helpers.AngleDifference( angle, this.Direction );

                    //Thread.Sleep( 0 );

                    if ( this.Profile.GetOptionValue<bool>( "QUICK_TURN" ) && angleDif > Helpers.Rad( 1d ) )
                    {
                        this.FaceDirection( angle, yangle );
                        this.Camera.SetRotation( ( float ) angle );
                        angleDif = Helpers.AngleDifference( angle, this.Direction );
                        Keyboard.HoldKey( Profile.HotKeys.MOVE_FORWARD.Key );
                    }
                    else
                    {
                        this.FaceDirection( this.Direction, yangle ); // change only 'Y' angle with 'faceDirection'.

                        if ( angleDif > Helpers.Rad( 15 ) )
                        {
                            if ( Helpers.AngleDifference( angle, this.Direction + 0.01 ) < angleDif )
                            {
                                Keyboard.ReleaseKey( Profile.HotKeys.ROTATE_RIGHT.Key );
                                Keyboard.HoldKey( Profile.HotKeys.ROTATE_LEFT.Key );
                                Thread.CurrentThread.Join( 100 );
                            }
                            else
                            {
                                Keyboard.ReleaseKey( Profile.HotKeys.ROTATE_LEFT.Key );
                                Keyboard.HoldKey( Profile.HotKeys.ROTATE_RIGHT.Key );
                                Thread.CurrentThread.Join( 100 );
                                //turning = true;
                            }
                        }
                        else if ( angleDif > Helpers.Rad( 1 ) )
                        {
                            if ( this.Profile.GetOptionValue<bool>( "QUICK_TURN" ) )
                            {
                                this.Camera.SetRotation( ( float ) angle );
                            }

                            this.FaceDirection( angle, yangle );
                            Keyboard.ReleaseKey( Profile.HotKeys.ROTATE_LEFT.Key );
                            Keyboard.ReleaseKey( Profile.HotKeys.ROTATE_RIGHT.Key );
                            Keyboard.HoldKey( Profile.HotKeys.MOVE_FORWARD.Key );
                            //turning = false;
                        }
                        else
                        {
                            Keyboard.HoldKey( Profile.HotKeys.MOVE_FORWARD.Key );
                        }
                        Thread.CurrentThread.Join( 10 );
                    }
                }
            }
            catch ( ThreadAbortException )
            {
                //This is simply to be able to abort the task and go back gracefully.
            }
        }

        private bool FixRunningDirection( Waypoint waypoint )
        {
            var angle = Math.Atan2( waypoint.Z - this.Z, waypoint.X - this.X );
            var yangle = 0d;
            if ( waypoint.Y != null )
            {
                yangle = Math.Atan2( ( ( float ) waypoint.Y ) - this.Y, Math.Pow( ( Math.Pow( ( waypoint.X - this.X ), 2 ) + Math.Pow( ( waypoint.Z - this.Z ), 2 ) ), .5 ) );
            }

            var angleDif = Helpers.AngleDifference( angle, this.Direction );

            // QUICK_TURN only
            if ( this.Profile.GetOptionValue<bool>( "QUICK_TURN" ) )
            {
                this.FaceDirection( angle, yangle );
                this.Camera.SetRotation( ( float ) angle );
                //camera:setRotation(angle);
                angleDif = Helpers.AngleDifference( angle, this.Direction );
            }
            else
            {
                this.FaceDirection( this.Direction, yangle );
                // change only 'Y' angle with 'faceDirection'.
            }

            // If more than X degrees off, correct before moving.
            var rotateStartTime = DateTime.Now;
            var turningDir = -1; // 0 = left, 1 = right

            while ( angleDif > Helpers.Rad( 65 ) )
            {
                if ( this.HP < 1 || this.Alive == false )
                {
                    return false;
                }

                if ( ( DateTime.Now - rotateStartTime ).Seconds > 3.0 )
                {
                    // Sometimes both left and right rotate get stuck down.
                    // Press them both to make sure they are fully released.
                    Keyboard.KeyPress( Profile.HotKeys.ROTATE_LEFT.Key );
                    Keyboard.KeyPress( Profile.HotKeys.ROTATE_RIGHT.Key );

                    // we seem to have been distracted, take a step back.
                    Keyboard.KeyPress( Profile.HotKeys.MOVE_BACKWARD.Key );
                    rotateStartTime = DateTime.Now;
                }

                if ( Helpers.AngleDifference( angle, this.Direction + 0.01 ) < angleDif )
                {
                    // rotate left
                    Keyboard.ReleaseKey( Profile.HotKeys.ROTATE_RIGHT.Key );
                    Keyboard.HoldKey( Profile.HotKeys.ROTATE_LEFT.Key );

                    //this.faceDirection( angle );
                }
                else
                {
                    // rotate right
                    Keyboard.ReleaseKey( Profile.HotKeys.ROTATE_LEFT.Key );
                    Keyboard.HoldKey( Profile.HotKeys.ROTATE_RIGHT.Key );
                }
                //this.faceDirection( angle );

                Thread.CurrentThread.Join( 50 );
                angleDif = Helpers.AngleDifference( angle, this.Direction );
            }
            this.LastDistImprove = DateTime.Now;
            Keyboard.ReleaseKey( Profile.HotKeys.ROTATE_RIGHT.Key );
            Keyboard.ReleaseKey( Profile.HotKeys.ROTATE_LEFT.Key );
            return true;
        }

        private bool PassedPoint( Waypoint lpos, Waypoint point )
        {
            const int posbuffer = 5;
            //return false;

            if ( lpos.X < point.X && this.X < point.X - posbuffer )
            {
                return false;
            }
            if ( lpos.X > point.X && this.X > point.X + posbuffer )
            {
                return false;
            }
            if ( lpos.Z < point.Z && this.Z < point.Z - posbuffer )
            {
                return false;
            }

            return !( lpos.Z > point.Z ) || !( this.Z > point.Z + posbuffer );
        }

        public void UpdateNature()
        {
            var tmp = this.GetBuff( "503827" );
            if ( tmp != null )
            { // has natures power
                this.Nature = tmp.Level + 1;
            }
            else
            {
                this.Nature = 0;
            }
        }

        internal void UpdatePsi()
        {
            this.Psi = ( int ) Memory.ReadRepeat<uint>( Bot.Instancia.ProcId, Addresses.psi );
        }

        public bool CheckPotions()
        {
            // If we need to use a heal over time potion
            if ( ( this.HP / this.MaxHP * 100 ) < this.Profile.GetOptionValue<int>( "HP_LOW_POTION" ) &&
                 ( DateTime.Now - this.PotionLastUseTime ).TotalSeconds > 15 )
            {
                var item = this.Inventory.BestAvailableConsumable( ConsumableType.HoT );
                if ( item != null )
                {
                    if ( this.Casting )
                    {
                        this.WaitTillCastingEnds();
                    } // wait if still casting minus undercut
                    var checkItemName = item.Name;
                    item.Update();
                    if ( checkItemName != item.Name )
                    {
                        //cprintf(cli.yellow, language[18], tostring(checkItemName), tostring(item.Name));
                        item.Update();
                    }
                    else
                    {
                        item.Use();
                        this.PotionHpUsed = this.PotionHpUsed + 1; // counts use of HP potions

                        //cprintf(cli.green, language[10], 		// Using HP potion this.HP, this.MaxHP, this.HP/this.MaxHP*100,item.Name, item.ItemCount);
                        this.RestWhileCheckingForWaypoint( 1000 );
                        if ( this.Fighting )
                        {
                            Thread.CurrentThread.Join( 1000 );
                        }

                        this.PotionLastUseTime = DateTime.Now;
                    }

                    return true;
                }
                else
                { // potions empty
                    if ( ( DateTime.Now - this.PotionLastHpEmptyTime ).TotalSeconds > 16 )
                    {
                        //cprintf(cli.yellow, language[17], inventory.MaxSlots); 		// No more (usable) hp potions
                        this.PotionLastHpEmptyTime = DateTime.Now;
                        // full inventory Update if potions empty
                        if ( ( DateTime.Now - this.InventoryLastUpdate ).Seconds >
                             this.Profile.GetOptionValue<int>( "INV_UPDATE_INTERVAL" ) )
                        {
                            this.InventoryDoUpdate = true;
                        }
                    }
                }
            }

            // If we need to use a mana over time potion(if we even have mana)
            if ( this.MaxMana > 0 )
            {
                if ( ( this.Mana / this.MaxMana * 100 ) < this.Profile.GetOptionValue<int>( "MP_LOW_POTION" ) &&
                     ( DateTime.Now - this.PotionLastUseTime ).TotalSeconds > 15 )
                {
                    var item = this.Inventory.BestAvailableConsumable( ConsumableType.MoT );
                    if ( item != null )
                    {
                        // Thread.CurrentThread.Join(this.Profile.GetOptionValue<int>("SKILL_USE_PRIOR"));	// potions can be drubk before cast/skill is finished
                        if ( this.Casting )
                        {
                            this.WaitTillCastingEnds();
                        } // wait if still casting minus undercut
                        //var unused,unused,checkItemName = Helpers.RoMScript("GetBagItemInfo(" + item.SlotNumber + ")");
                        // I think this check here is useless now
                        var checkItemName = item.Name;
                        item.Update();
                        if ( checkItemName != item.Name )
                        {
                            //cprintf(cli.yellow, language[18], tostring(checkItemName), tostring(item.Name));
                            item.Update();
                        }
                        else
                        {
                            item.Use();
                            this.PotionManaUsed = this.PotionManaUsed + 1; // counts use of mana potions

                            //cprintf(cli.green, language[11], 		// Using MP potion this.Mana, this.MaxMana, this.Mana/this.MaxMana*100,item.Name, item.ItemCount);
                            this.RestWhileCheckingForWaypoint( 1000 );
                            if ( this.Fighting )
                            {
                                Thread.CurrentThread.Join( 1000 );
                            }

                            this.PotionLastUseTime = DateTime.Now;
                        }

                        return true; // avoid invalid/use count of
                    }
                    else
                    { // potions empty
                        if ( ( DateTime.Now - this.PotionLastManaEmptyTime ).TotalSeconds > 16 )
                        {
                            //cprintf(cli.yellow, language[16], inventory.MaxSlots); 		// No more (usable) mana potions
                            this.PotionLastManaEmptyTime = DateTime.Now;
                            // full inventory Update if potions empty
                            if ( ( DateTime.Now - this.InventoryLastUpdate ).Seconds >
                                 this.Profile.GetOptionValue<int>( "INV_UPDATE_INTERVAL" ) )
                            {
                                this.InventoryDoUpdate = true;
                            }
                        }
                    }
                }
            }

            // If we need to use a heal potion
            if ( ( this.HP / this.MaxHP * 100 ) < this.Profile.GetOptionValue<int>( "HP_LOW_POTION" ) &&
                 ( DateTime.Now - this.PotionLastOnceUseTime ).TotalSeconds > 60 )
            {
                var item = this.Inventory.BestAvailableConsumable( ConsumableType.Heal );
                if ( item != null )
                {
                    if ( this.Casting )
                    {
                        this.WaitTillCastingEnds();
                    } // wait if still casting minus undercut
                    var checkItemName = item.Name;
                    item.Update();
                    if ( checkItemName != item.Name )
                    {
                        //cprintf(cli.yellow, language[18], tostring(checkItemName), tostring(item.Name));
                        item.Update();
                    }
                    else
                    {
                        item.Use();
                        this.PotionHpOnceUsed++; // counts use of HP potions

                        //cprintf(cli.green, language[10], 		// Using HP potionthis.HP, this.MaxHP, this.HP/this.MaxHP*100,item.Name, item.ItemCount);
                        if ( this.Fighting )
                        {
                            Thread.CurrentThread.Join( 1000 );
                        }

                        this.PotionLastOnceUseTime = DateTime.Now;
                    }

                    return true;
                }
                else
                { // potions empty
                    if ( ( DateTime.Now - this.PotionLastHpOnceEmptyTime ).TotalSeconds > 16 )
                    {
                        ////cprintf(cli.yellow, language[17], inventory.MaxSlots); 		// No more (usable) hp potions
                        this.PotionLastHpOnceEmptyTime = DateTime.Now;
                        // full inventory Update if potions empty
                        if ( ( DateTime.Now - this.InventoryLastUpdate ).Seconds >
                             this.Profile.GetOptionValue<int>( "INV_UPDATE_INTERVAL" ) )
                        {
                            this.InventoryDoUpdate = true;
                        }
                    }
                }
            }

            // If we need to use a mana potion(if we even have mana)
            if ( this.MaxMana > 0 )
            {
                if ( ( this.Mana / this.MaxMana * 100 ) < this.Profile.GetOptionValue<int>( "MP_LOW_POTION" ) &&
                     ( DateTime.Now - this.PotionLastOnceUseTime ).TotalSeconds > 60 )
                {
                    var item = this.Inventory.BestAvailableConsumable( ConsumableType.Mana );
                    if ( item != null )
                    {
                        // Thread.CurrentThread.Join(this.Profile.GetOptionValue<int>("SKILL_USE_PRIOR"));	// potions can be drubk before cast/skill is finished
                        if ( this.Casting )
                        {
                            this.WaitTillCastingEnds();
                        } // wait if still casting minus undercut
                        //var unused,unused,checkItemName = Helpers.RoMScript("GetBagItemInfo(" + item.SlotNumber + ")");
                        // I think this check here is useless now
                        var checkItemName = item.Name;
                        item.Update();
                        if ( checkItemName != item.Name )
                        {
                            //cprintf(cli.yellow, language[18], tostring(checkItemName), tostring(item.Name));
                            item.Update();
                        }
                        else
                        {
                            item.Use();
                            this.PotionManaOnceUsed++; // counts use of mana potions

                            //cprintf(cli.green, language[11], 		// Using MP potionthis.Mana, this.MaxMana, this.Mana/this.MaxMana*100,item.Name, item.ItemCount);
                            if ( this.Fighting )
                            {
                                Thread.CurrentThread.Join( 1000 );
                            }

                            this.PotionLastOnceUseTime = DateTime.Now;
                        }

                        return true; // avoid invalid/use count of
                    }
                    else
                    { // potions empty
                        if ( ( DateTime.Now - this.PotionLastManaOnceEmptyTime ).TotalSeconds > 16 )
                        {
                            ////cprintf(cli.yellow, language[16], inventory.MaxSlots); 		// No more (usable) mana potions
                            this.PotionLastManaOnceEmptyTime = DateTime.Now;
                            // full inventory Update if potions empty
                            if ( ( DateTime.Now - this.InventoryLastUpdate ).Seconds >
                                 this.Profile.GetOptionValue<int>( "INV_UPDATE_INTERVAL" ) )
                            {
                                this.InventoryDoUpdate = true;
                            }
                        }
                    }
                }
            }

            return false;
        }

        private bool RestWhileCheckingForWaypoint( int duration )
        {
            if ( Bot.Instancia.CurrentWaypointList.Count > 0 && this.Moving && !this.Fighting )
            {
                // rest for _duration but if moving stop when reaching waypoint
                var starttime = DateTime.Now;
                var curWp = Bot.Instancia.CurrentWaypointList.Current;
                var lastdist = Helpers.Distance( this.X, this.Z, curWp.X, curWp.Z );
                do
                {
                    var startdist = lastdist;
                    Thread.CurrentThread.Join( 10 );
                    lastdist = Helpers.Distance( this.X, this.Z, curWp.X, curWp.Z );
                    if ( lastdist < 10 || lastdist > startdist )
                    { // && wp reached || moving away
                        return false;
                    }
                } while ( ( DateTime.Now - starttime ).TotalMilliseconds < duration / 1000 );
            }
            else
            {
                Thread.CurrentThread.Join( duration );
            }
            return true;
        }

        public void WaitTillCastingEnds()
        {
            var prior = Helpers.SkillUsePrior;
            while ( this.Casting && this.RemainingCastTime.Left > ( prior / 1000 ) )
            {
                Thread.CurrentThread.Join( 10 );
            }
        }

        public void CheckAddress()
        {
            var tmpAddress = Memory.ReadRepeatPtr<uint>( Addresses.staticbase_char, Addresses.charPtr_offset );
            if ( tmpAddress != this.Address && tmpAddress != 0 )
            {
                this.Update();
            }
        }

        public void Fight()
        {
            this.fightTimer.Reset();
            this.UpdateTarget();
            if ( !this.HasTarget )
            {
                return;
            }
            Timer T = null;
            var timedAttack = new Action<bool>( stop =>
                                                {
                                                    if ( !stop )
                                                    {
                                                        T = new Timer( state =>
                                                                       {
                                                                           //Bot.Debug( "Comenzando timedAttack." );
                                                                           if ( this.TargetPtr == 0 )
                                                                           {
                                                                               return;
                                                                           }

                                                                           if ( this.Target == null || !this.Target.Alive || ( this.Target.HP / this.Target.MaxHP ) <= 0.1 )
                                                                           {
                                                                               return;
                                                                           }
                                                                           if ( Casting )
                                                                           {
                                                                               do
                                                                               {
                                                                                   Helpers.Yrest( 10 );
                                                                               } while ( Casting );
                                                                           }
                                                                           this.Attack();
                                                                       }, null, 0, 2000 );
                                                    }
                                                    else
                                                    {
                                                        if ( T != null )
                                                        {
                                                            T.Dispose();
                                                        }
                                                    }
                                                } );

            this.IgnoreTarget = this.Target.Address;

            if ( this.Class1 == PawnClass.Warden && this.Pet != null && ( this.Pet.Id == 102297 || this.Pet.Id == 102324 || this.Pet.Id == 102803 ) )
            {
                this.Pet.StartCombat();
            }

            this.Fighting = true;

            Bot.Printf( Color.Green, Resources.EngagingEnemyInCombat, this.Target.Name );

            this.fightTimer.Start();
            this.FightStartTime = Helpers.GameTime;
            var moveCloserCounter = 0; // count move closer trys
            this.CastToTarget = 0; // reset counter cast at enemy target
            this.RangedPull = false; // flag for timed ranged pull for melees
            float hfStartDist = 0; // distance to mob where we start the fight

            //	check if timed ranged pull for melee
            if ( this.Profile.GetOptionValue<string>( "COMBAT_TYPE" ) == "melee" &&
                 this.Profile.GetOptionValue<bool>( "COMBAT_RANGED_PULL" ) &&
                 this.Battling != true )
            {
                Bot.Print( Resources.WeBeginWithRangedPulling, Color.Green );
                this.RangedPull = true;
            }

            //normal melee attack only if ranged pull isn't used
            if ( this.Profile.GetOptionValue<string>( "COMBAT_TYPE" ) == "melee" && !this.RangedPull )
            {
                if ( Bot.DebugFight )
                {
                    Debug.WriteLine( "Comenzando porque es melee" );
                }
                timedAttack( false );
            }

            this.FailedCastInARow = 0;
            this.BreakFight = false; // flag to avoid kill counts for breaked fights
            this.BreakFromFight = false; // For users to manually break from fight using player:breakFight()
            if ( Bot.DebugFight )
            {
                Bot.Debug( "Entering the Fight loop." );
            }
            while ( this.HasTarget && this.Running )
            {
                if ( !this.Alive || this.HP < 1 )
                {
                    if ( this.Profile.GetOptionValue<string>( "COMBAT_TYPE" ) == "melee" )
                    {
                        timedAttack( true );
                    }
                    this.Fighting = false;
                    this.BreakFight = true;
                    if ( Bot.DebugFight )
                    {
                        Bot.Debug( "Terminando pelea, objetivo muerto." );
                    }
                    break;
                }

                if ( this.BreakFromFight )
                {
                    this.BreakFight = true;
                    if ( Bot.DebugFight )
                    {
                        Bot.Debug( "Terminando pelea a petición del usuario ( BreakFromFight )." );
                    }
                    break;
                }

                if ( !this.Running )
                {
                    if ( Bot.DebugFight )
                    {
                        Bot.Debug( "Terminando pelea a petición del usuario ( Stop o Pause )." );
                    }
                    break;
                }

                // Long time break: Exceeded max fight time (without hurting enemy) so break fighting
                var maxFightTime = this.Profile.GetOptionValue<int>( "MAX_FIGHT_TIME" );
                if ( Helpers.GameTime - this.FightStartTime > maxFightTime )
                {
                    //Debug.WriteLine( "Target.LastDamage = {0}", Target.LastDamage );
                    var lastHit = Helpers.GameTime - this.LastHitTime;
                    if ( this.Target.LastDamage <= 0 || lastHit > maxFightTime )
                    {
                        Bot.Print( Resources.TakingTooLongToDamageTargetBreakingSequence );
                        //Bot.Printf( "Target.LastDamage = {0} / (GameTime - LastHitTime) = {1} / MAX_FIGHT_TIME = {2}", this.Target.LastDamage, lastHit, maxFightTime );
                        //Taking too long to damage target
                        timedAttack( true );
                        this.AddMobToIgnoreList( this.Target.Address );
                        this.ClearTarget();
                        if ( this.Battling )
                        {
                            Keyboard.HoldKey( Profile.HotKeys.MOVE_BACKWARD.Key );
                            Thread.CurrentThread.Join( 1000 );
                            Keyboard.ReleaseKey( Profile.HotKeys.MOVE_BACKWARD.Key );
                            if ( Bot.DebugFight )
                            {
                                Bot.Debug( "Nos movimos hacia atrás porque ¿no hicimos daño?" );
                            }
                        }

                        this.BreakFight = true;
                        if ( Bot.DebugFight )
                        {
                            Bot.Debug( "Terminando pelea porque no hicimos daño." );
                        }
                        break;
                    }
                }

                var dist = Helpers.Distance( this.X, this.Z, this.Target.X, this.Target.Z );
                if ( hfStartDist <= 0 )
                {
                    hfStartDist = dist;
                }

                if ( this.RangedPull )
                {
                    if ( dist < this.Profile.GetOptionValue<int>( "MELEE_DISTANCE" ) )
                    {
                        Bot.Print( Resources.RangedPullingFinishedMobInMeleeDistance, Color.Green );
                        this.RangedPull = false;
                    }
                    else if ( this.AggroStartTime.IsRunning && this.AggroStartTime.ElapsedMilliseconds > 3000 )
                    {
                        Bot.Print( Resources.RangedPullingAfter3SecWaitFinished, Color.Green );
                        this.RangedPull = false;
                    }
                    else if ( dist >= hfStartDist - 45 && this.AggroStartTime.IsRunning && this.AggroStartTime.ElapsedMilliseconds > 1000 )
                    {
                        Bot.Print( Resources.RangedPullingFinishedMobNotReallyMoving, Color.Green );
                        this.RangedPull = false;
                    }

                    if ( !this.RangedPull && this.Profile.GetOptionValue<string>( "COMBAT_TYPE" ) == "melee" )
                    {
                        if ( Bot.DebugFight )
                        {
                            Bot.Debug( "Comenzando pelea melee." );
                        }
                        timedAttack( false );
                    }
                }

                if ( dist < 5.0 && !this.Casting )
                {
                    if ( this.TargetPtr == this.Address )
                    {
                        timedAttack( true );
                        this.ClearTarget();
                        this.BreakFight = true;
                        break;
                    }
                    Bot.Print( Resources.TooCloseBackingUp );
                    Keyboard.HoldKey( Profile.HotKeys.MOVE_BACKWARD.Key );
                    Thread.CurrentThread.Join( 200 );
                    Keyboard.ReleaseKey( Profile.HotKeys.MOVE_BACKWARD.Key );
                    dist = Helpers.Distance( this.X, this.Z, this.Target.X, this.Target.Z );
                }

                var sugestedRange = this.Profile.GetOptionValue<int>( "MELEE_DISTANCE" );
                if ( sugestedRange == 0 )
                {
                    sugestedRange = 45;
                }

                if ( this.Profile.GetOptionValue<string>( "COMBAT_TYPE" ) == "ranged" || this.RangedPull )
                {
                    sugestedRange = this.Profile.GetOptionValue<int>( "COMBAT_DISTANCE" ) > 0 ? this.Profile.GetOptionValue<int>( "COMBAT_DISTANCE" ) : 155;
                }

                if ( this.Battling && this.Target.LastDamage <= 0 && this.Target.TargetPtr != this.Address && !this.Target.TargetIsFriend() )
                {
                    var enemy = this.FindEnemy( true, 0, this.EvalTargetDefault );
                    if ( enemy != null && enemy.Address != this.TargetPtr )
                    {
                        Bot.Printf( Color.Green, Resources.AggroDuringFirstStrikeCastAbortThatCastTarget, this.Target.Name );
                        this.ClearTarget();
                        this.BreakFight = true;
                        break;
                    }
                }

                var success = false;
                if ( ( int ) dist > sugestedRange && !this.Casting )
                {
                    moveCloserCounter++;
                    if ( moveCloserCounter > 3 && ( this.Profile.GetOptionValue<string>( "COMBAT_TYPE" ) == "ranged" || this.RangedPull ) )
                    {
                        Bot.Print( Resources.ToManyTriesToMoveCloserWeStopAttackingThatTarget, Color.Green );
                        this.ClearTarget();
                        this.BreakFight = true;
                        break;
                    }

                    Bot.Printf( Resources.MovingInSuggestedRangeDistance, sugestedRange, dist );

                    if ( this.Profile.GetOptionValue<string>( "COMBAT_TYPE" ) == "ranged" || this.RangedPull )
                    {
                        success = this.MoveTo( this.Target, true, true, sugestedRange ).Item1;
                        if ( success )
                        {
                            Keyboard.ReleaseKey( Profile.HotKeys.MOVE_FORWARD.Key );
                        }
                    }
                    else
                    {
                        success = this.MoveTo( this.Target, true, false, 50 ).Item1;
                        if ( this.Profile.GetOptionValue<string>( "COMBAT_TYPE" ) == "melee" )
                        {
                            if ( Bot.DebugFight )
                            {
                                Bot.Debug( "Comenzando pelea melee luego de movernos." );
                            }
                            timedAttack( false );
                        }
                    }

                    if ( !this.HasTarget )
                    {
                        Bot.Print( Resources.TargetLost, Color.Green );
                        this.ClearTarget();
                        this.BreakFight = true;
                        break;
                    }

                    if ( !success )
                    {
                        this.Unstick();
                    }
                }
                else if ( sugestedRange >= ( int ) dist && ( this.Profile.GetOptionValue<int?>( "COMBAT_STOP_DISTANCE" ) == null || dist <= this.Profile.GetOptionValue<int>( "COMBAT_STOP_DISTANCE" ) ) )
                {
                    Keyboard.ReleaseKey( Profile.HotKeys.MOVE_FORWARD.Key );
                }

                if ( !this.Target.Alive )
                {
                    Bot.Warning( "Fight ended, target dead." );
                    break;
                }

                this.FaceTarget();

                if ( this.CheckPotions() | this.CheckSkills( false, this.Target ) )
                {
                    this.LastDistImprove = DateTime.Now;
                    this.WaitTillCastingEnds();
                }
                else if ( this.CastToTarget == 0 )
                {
                    this.RangedPull = false;
                }

                if ( this.BreakFight )
                {
                    Bot.Print( Resources.TargetLost, Color.Green );
                    break;
                }

                Thread.CurrentThread.Join( 100 );
            }
            this.fightTimer.Stop();
            //Bot.Debug( "Left Fight loop." );

            this.ResetSkills();
            this.CastToTarget = 0;

            if ( !this.Running )
            {
                ReleaseKeys();
                this.Fighting = false;
                //Bot.Debug( "Terminando pelea " + ( this.Paused ? "pausado por el usuario." : "detenido por el usuario." ) );
                return;
            }

            if ( this.Profile.GetOptionValue<string>( "COMBAT_TYPE" ) == "melee" )
            {
                //Bot.Debug( "Terminando pelea melee." );
                timedAttack( true );
            }

            if ( !this.BreakFight && this.Target != null && !this.Target.Alive )
            {
                var expGain = this.LastExp;
                var tpGain = this.LastTp;
                this.Fights++;
                var weKill = StatisticsKilledMobs.Count( m => m.Name == this.Target.Name ) + 1;
                Task.Factory.StartNew( () =>
                                       {
                                           var km = new KilledMob
                                           {
                                               Name = this.Target.Name,
                                               Level = this.Target.Level,
                                               Exp = expGain,
                                               Tp = tpGain,
                                               Time = this.fightTimer.Elapsed
                                           };
                                           SaveKilledMob( km );
                                       } );
                //_statisticsKilledMobs.Add( km );
                //if ( KilledMobs.ContainsKey( this.Target.Name ) )
                //{
                //    KilledMobs[ this.Target.Name ]++;
                //}
                //else
                //{
                //    KilledMobs[ this.Target.Name ] = 1;
                //    //KilledMobs.Add( this.Target.Name, 1 );
                //}
                Bot.Printf( Color.Green, Resources.FightFinishedKilledXMobs + "\u2028Exp gained: " + expGain + ", TP gained: " + tpGain + "\u2028It took: " + this.fightTimer.Elapsed.ToString( "mm\\:ss" ) + " to kill it.", weKill, this.Target.Name, this.Fights, Bot.RunningTime.ToString( "hh\\:mm\\:ss" ) );
            }
            else
            {
                Bot.Print( Resources.CurrentFightAborted, Color.Green );
            }

            this.Profile.OnLeaveCombat();

            if ( !this.BreakFight )
            {
                //Bot.Debug( "Fight ended, looting." );
                //var posBeforeLoot = this.Position;
                this.Loot( this.Target );
                //if ( this.DistanceToWaypoint( posBeforeLoot ) > 50 )
                //{
                //    Bot.Printf( "Recalculating WP, current pos: {0}, pos before loot: {1}", WaypointList.Current, posBeforeLoot );
                //    var next = WaypointList.GetNextWayPoint( 1 );
                //    var wpPrev = Helpers.GetNearestSegmentPoint( this.X, this.Z, WaypointList.Current.X, WaypointList.Current.Z, posBeforeLoot.X, posBeforeLoot.Z );
                //    var wpNext = Helpers.GetNearestSegmentPoint( this.X, this.Z, WaypointList.Current.X, WaypointList.Current.Z, next.X, next.Z );
                //    if ( this.DistanceToWaypoint( wpPrev ) < this.DistanceToWaypoint( wpNext ) )
                //    {
                //        Bot.Printf( "Recalculating WP, new WP (prev): {0}", wpPrev );
                //        this.MoveTo( wpPrev, false, true );
                //    }
                //    else
                //    {
                //        Bot.Printf( "Recalculating WP, new WP (next): {0}", wpNext );
                //        this.MoveTo( wpNext, false, true );
                //    }
                //    WaypointList.Current = WaypointList.GetNearestWaypoint( this.X, this.Z );
                //}
            }

            Keyboard.ReleaseKey( Profile.HotKeys.MOVE_FORWARD.Key );

            if ( !this.Battling || this.FindEnemy( true, 0, this.EvalTargetDefault ) == null )
            {
                this.LootAll();
            }

            if ( this.TargetPtr != 0 )
            {
                this.ClearTarget();
            }

            this.Fighting = false;
            this.LastSkill = new Skill();
            //t.Dispose();
            timedAttack( true );
            Thread.CurrentThread.Join( 10 );
        }

        private Waypoint Position
        {
            get { return new Waypoint( this.X, this.Z, this.Y ); }
        }

        private void SaveKilledMob( KilledMob mob )
        {
            _statisticsKilledMobs.Add( mob );
            //using ( var session = Database.Database.DocumentStore.OpenSession() )
            //{
            //    session.Store( mob, "CharStatistics/" + this.Name + "/KilledMobs/" );
            //    session.SaveChanges();
            //}
        }

        public void Loot( Pawn target = null )
        {
            var loot = this.Profile.GetOptionValue<bool>( "LOOT" );
            var lootSigils = this.Profile.GetOptionValue<bool?>( "LOOT_SIGILS" ) ?? true;
            if ( !loot && !lootSigils )
            {
                if ( Bot.DebugLoot )
                {
                    Bot.Debug( "Don't loot reason: LOOT != true and LOOT_SIGILS != true" );
                }
                return;
            }

            if ( loot )
            {
                if ( this.TargetPtr == 0 )
                {
                    if ( Bot.DebugLoot )
                    {
                        Bot.Debug( "Don't loot reason: self.TargetPtr == 0" );
                    }
                    return;
                }

                var lootInCombat = this.Profile.GetOptionValue<bool>( "LOOT_IN_COMBAT" );
                if ( this.Battling && !lootInCombat && this.FindEnemy( true ) != null )
                {
                    this.ClearTarget();
                    if ( Bot.DebugLoot )
                    {
                        Bot.Debug( "Don't loot, reason: We got aggro and LOOT_IN_COMBAT != true, loot skipped." );
                    }
                    return;
                }
                if ( target == null )
                {
                    target = new Pawn( this.targetPtr );
                }
                if ( !target.Exists )
                {
                    if ( Bot.DebugLoot )
                    {
                        Bot.Debug( "Don't loot reason: target == null or target.Address == 0." );
                    }
                    return;
                }

                var dist = Helpers.Distance( this, target );
                var lootdist = 100;

                // If already in looting range then stop
                if ( dist < 20 )
                {
                    Keyboard.ReleaseKey( Profile.HotKeys.MOVE_FORWARD.Key );
                }

                // Set to combat distance; update later if loot distance is set
                if ( this.Profile.GetOptionValue<string>( "COMBAT_TYPE" ) == "ranged" )
                {
                    lootdist = this.Profile.GetOptionValue<int>( "COMBAT_DISTANCE" );
                }

                if ( this.Profile.GetOptionValue<int>( "LOOT_DISTANCE" ) > 0 )
                {
                    lootdist = this.Profile.GetOptionValue<int>( "LOOT_DISTANCE" );
                }

                if ( dist > lootdist )
                { // only loot when close by
                    if ( Bot.DebugLoot )
                    {
                        Bot.Debug( "Target too far away. Not looting." );
                    }
                    return;
                }

                var looten = new Action(
                        () =>
                        {
                            this.Attack();
                            Keyboard.ReleaseKey( Profile.HotKeys.MOVE_FORWARD.Key );
                            Thread.CurrentThread.Join( 200 );

                            var maxWaitTime = this.Profile.GetOptionValue<int>( "LOOT_TIME" ) + dist * 15;
                            var startWait = DateTime.Now;

                            while ( target.Lootable && this.Stance == 0 && ( DateTime.Now - startWait ).TotalMilliseconds < maxWaitTime )
                            {
                                if ( Math.Abs( this.ActualSpeed - 0.0f ) < 0.01 )
                                {
                                    this.Attack();
                                    Thread.CurrentThread.Join( 100 );
                                }
                                Thread.CurrentThread.Join( 100 );
                            }

                            var startTime = DateTime.Now;
                            while ( this.Stance != 0 && 2 > ( DateTime.Now - startTime ).TotalSeconds )
                            {
                                Thread.CurrentThread.Join( 50 );
                            }
                        } );

                var lootStart = DateTime.Now;
                while ( this.Running && target.Exists && !target.Lootable && ( DateTime.Now - lootStart ).TotalMilliseconds < 200 )
                {
                    Thread.CurrentThread.Join( 20 );
                }

                if ( target.Lootable )
                {
                    looten();
                }

                Helpers.RoMScript( "BootyFrame:Hide()" );
            }
            if ( lootSigils )
            {
                this.ClearTarget();
                this.LootSigils();
            }
        }

        private LootSigil GetNearestSigil()
        {
            var objTable = new ObjectsTable();
            objTable.Update();
            return objTable.Select( o => new LootSigil { Sigil = o, Dist = Helpers.Distance( o, this ) } ).Where( o => o.Sigil.Type == ObjectType.Sigil && o.Dist <= this.Profile.GetOptionValue<int>( "LOOT_DISTANCE" ) ).OrderBy( s => s.Dist ).FirstOrDefault();
        }

        private void LootSigils()
        {
            var sigil = this.GetNearestSigil();
            while ( this.Running && sigil != null )
            {
                var angle = Math.Atan2( sigil.Sigil.Z - this.Z, sigil.Sigil.X - this.X );
                var yangle = Math.Atan2( sigil.Sigil.Y - this.Y, Math.Pow( ( Math.Pow( ( sigil.Sigil.X - this.X ), 2 ) + Math.Pow( ( sigil.Sigil.Z - this.Z ), 2 ) ), .5 ) );
                var nY = this.Y + Math.Sin( yangle ) * ( sigil.Dist + 15 );
                var hypotenuse = Math.Pow( ( 1 - Math.Pow( Math.Sin( yangle ), 2 ) ), .5 );
                var nX = this.X + Math.Cos( angle ) * ( sigil.Dist + 15 ) * hypotenuse;
                var nZ = this.Z + Math.Sin( angle ) * ( sigil.Dist + 15 ) * hypotenuse;
                Bot.Printf( "Picking up sigil \"{0}\".", sigil.Sigil.Name );

                this.MoveTo( new Waypoint( ( float ) nX, ( float ) nZ, ( float? ) nY ), true );
                Helpers.Yrest( 400 + Helpers.Ping );
                sigil = this.GetNearestSigil();
            }
        }

        public void LootAll()
        {
            if ( !this.Profile.GetOptionValue<bool>( "LOOT_ALL" ) )
            {
                if ( Bot.DebugLoot )
                {
                    Bot.Debug( "Don't loot all reason: LOOT_ALL != true" );
                }
                return;
            }

            while ( this.Running )
            {
                if ( this.Inventory.IsFull )
                {
                    if ( Bot.DebugLoot )
                    {
                        Bot.Debug( "Don't loot all reason: Inventory full." );
                    }
                    return;
                }

                if ( this.Battling &&
                     this.FindEnemy( true, 0, this.EvalTargetDefault ) != null )
                {
                    if ( Bot.DebugLoot )
                    {
                        Bot.Debug( "Don't loot all reason: Battling && FindEnemy." );
                    }
                    break;
                }

                var lootableObj = this.FindNearestNameOrId( "", 0, this.EvalTargetLootable );

                if ( lootableObj == null )
                {
                    if ( Bot.DebugLoot )
                    {
                        Bot.Debug( "Don't loot all reason: lootableObj == null." );
                    }
                    break;
                }

                var lootable = new Pawn( lootableObj.Address );

                this.SetTarget( lootable );
                if ( this.TargetPtr != 0 )
                { // Target's still there.
                    this.Loot();
                    if ( this.FindEnemy( true, 0, this.EvalTargetDefault ) != null )
                    {
                        // Not looting because of aggro
                        if ( Bot.DebugLoot )
                        {
                            Bot.Debug( "Don't loot all reason: Aggro." );
                        }
                        return;
                    }
                    Thread.CurrentThread.Join( 50 );
                    if ( lootable.Lootable )
                    {
                        // Failed to loot. Add to ignore list
                        if ( this.IgnoreLootableList.Count > this.Profile.GetOptionValue<int>( "LOOT_IGNORE_LIST_SIZE" ) )
                        {
                            this.IgnoreLootableList.Clear();
                        }
                        this.IgnoreLootableList.Add( lootable.Address );
                        //LootIgnoreList[lootIgnoreListPos] = Lootable.Address
                    }
                }
            }
        }

        public bool EvalTargetLootable( int address, GameObject lootAbleTarget )
        {
            var target = lootAbleTarget != null ? new Pawn( lootAbleTarget.Address ) : new Pawn( address );
            if ( !target.Exists )
            {
                return false;
            }
            if ( !target.Lootable )
            {
                return false;
            }
            if ( this.IgnoreLootableList.Contains( target.Address ) )
            {
                return false;
            }

            // Check height difference
            if ( Math.Abs( target.Y - this.Y ) > 45 )
            {
                return false;
            }

            // check distance to target
            var dist = Helpers.Distance( this.X, this.Z, this.Y, target.X, target.Z, target.Y );
            var lootdist = 100;

            // Set to combat distance; Update later if loot distance is set
            if ( this.Profile.GetOptionValue<string>( "COMBAT_TYPE" ) == "ranged" )
            {
                lootdist = this.Profile.GetOptionValue<int>( "COMBAT_DISTANCE" );
            }
            if ( this.Profile.GetOptionValue<int>( "LOOT_DISTANCE" ) > 0 )
            {
                lootdist = this.Profile.GetOptionValue<int>( "LOOT_DISTANCE" );
            }

            if ( dist > lootdist ) // only loot when close by
            {
                return false;
            }

            Waypoint v;

            if ( Bot.Instancia.CurrentWaypointList.Mode == WaypointMode.Waypoints && Bot.Instancia.CurrentWaypointList.Count > 0 )
            {
                var pA = Bot.Instancia.CurrentWaypointList.GetNextWayPoint( -1 );
                var pB = Bot.Instancia.CurrentWaypointList.Current;

                v = Helpers.GetNearestSegmentPoint( this.X, this.Z, pA.X, pA.Z, pB.X, pB.Z );
            }
            else
            {
                v = new Waypoint( this.X, this.Z );
            }

            // use a bounding box first to avoid sqrt when not needed (sqrt is expensive)
            if ( target.X > ( v.X - lootdist ) &&
                 target.X < ( v.X + lootdist ) &&
                 target.Z > ( v.Z - lootdist ) &&
                 target.Z < ( v.Z + lootdist ) )
            {
                if ( Helpers.Distance( v.X, v.Z, target.X, target.Z ) > lootdist )
                {
                    if ( Bot.DebugLoot )
                    {
                        Bot.Print( "Unlooted monster dist > lootdist", Color.Yellow );
                    }
                    return false; // he is not a valid target
                }
            }
            else // must be too far away
            {
                if ( Bot.DebugLoot )
                {
                    Bot.Debug( "unlooted monster dist > lootdist" );
                }
                return false;
            }

            return true;
        }

        public void ResetSkills()
        {
            for ( var i = 0; i < this.Skills.Count; i++ )
            {
                var skill = this.Skills[ i ];
                if ( skill.Toggled && ( skill.Type == SkillType.Damage || skill.Type == SkillType.DoT || skill.Type == SkillType.Debuff ) )
                {
                    skill.Toggled = false;
                }
                skill.Used = 0;
                this.Skills[ i ] = skill;
            }
        }

        private void FaceTarget()
        {
            if ( this.CastToTarget != 0 && this.LastSkill.CastTime > 0 && this.LastSkill.ClickToCast )
            {
                return;
            }
            this.UpdateTarget();
            if ( !this.HasTarget )
            {
                return;
            }

            if ( !this.Target.Exists || this.Target.Type != ObjectType.Monster )
            {
                return;
            }

            var angle = Math.Atan2( this.Target.Z - this.Z, this.Target.X - this.X );
            var yangle = Math.Atan2( this.Target.Y - this.Y, Math.Pow( ( Math.Pow( ( this.Target.X - this.X ), 2 ) + Math.Pow( ( this.Target.Z - this.Z ), 2 ) ), .5 ) );
            this.Rotate( angle, yangle );
        }

        private bool ShouldTryToCastAgain( bool onlyFriendly, Skill skill = null, Pawn target = null )
        {
            if ( !onlyFriendly )
            {
                if ( target == null )
                {
                    this.moveCloseCounter = 0;
                    return false;
                }
                if ( !this.tryiesAtTarget.ContainsKey( target.Address ) )
                {
                    this.tryiesAtTarget.Add( target.Address, 0 );
                }

                if ( !target.Alive || !target.Exists )
                {
                    this.moveCloseCounter = 0;
                    return false;
                }
                if ( target.HP < 1 )
                {
                    this.moveCloseCounter = 0;
                    return false;
                }
                var lastTime = Helpers.GetLastWarning( this.CollisionMsg, 2 );
                if ( lastTime > 0 && ( lastTime - this.checkskillsLastCollisiontryAgain ) > 0.001 )
                {
                    this.checkskillsLastCollisiontryAgain = lastTime;
                    if ( this.tryiesAtTarget[ target.Address ] < 3 )
                    {
                        if ( !this.Battling )
                        {
                            this.tryiesAtTarget[ target.Address ]++;
                            this.FaceTarget();
                            Bot.Printf( Color.Yellow, Resources.WeGoingToTryToMoveCloserBecauseOfAnObstacle, this.tryiesAtTarget[ target.Address ] );
                            Keyboard.HoldKey( Profile.HotKeys.MOVE_FORWARD.Key );
                            Thread.CurrentThread.Join( 1000 );
                            Keyboard.ReleaseKey( Profile.HotKeys.MOVE_FORWARD.Key );
                            this.WaitTillStopMoving();
                            this.LastDistImprove = DateTime.Now;
                            return true;
                        }
                    }
                    return false;
                }
            }

            return false;
        }

        public bool CheckSkills( bool onlyFriendly = false, Pawn target = null )
        {
            this.moveCloseCounter = 0;
            var tryToFaceTarget = 0;

            var used = false;
            //if settings.profile.options.DISMOUNT == false && player.Mounted { return false }

            target = target ?? new Pawn( this.TargetPtr );
            var useQueue = false; // Whether to use the regular profile skills

            if ( this.SkillQueue.Count > 0 )
            {
                // Queue is ! empty. See if we can cast anything
                var skill = this.SkillQueue.Peek();
                if ( !TakingTooLongToDamageTarget( onlyFriendly, target ) )
                {
                    if ( skill.CanUse( false, target ) )
                    {
                        if ( skill.CastTime > 0 )
                        {
                            Keyboard.ReleaseKey( Profile.HotKeys.MOVE_FORWARD.Key );
                            // Wait to stop only if ! an instant cast spell
                            this.WaitTillStopMoving();
                        }
                        var resu = this.CastImpl( skill );
                        this.Skills[ this.Skills.IndexOf( skill ) ] = skill;
                        if ( ( resu is bool && ( bool ) resu == false ) || ( resu is string && resu.ToString() == "failed to cast" ) )
                        {
                            CheckSystemMessages( tryToFaceTarget );
                        }
                        else
                        {
                            used = true;
                            this.LastSkill = skill;
                            this.SkillQueue.Dequeue();
                        }
                    }
                    else
                    {
                        if ( skill.Blocking )
                        {
                            useQueue = true;
                        }
                        else
                        {
                            this.SkillQueue.Dequeue();
                        }
                    }
                }
            }
            else
            {
                // Queue is empty, continue like normal
                useQueue = true;
            }
            if ( useQueue || this.SkillQueue.Count == 0 )
            {
                var lastDistToWp = 0f;
                var attackSkillUsed = false; // Used for priority casting
                for ( var index = 0; index < this.Skills.Count; index++ )
                {
                    var v = this.Skills[ index ];
                    try
                    {
                        Monitor.Enter( v );
                        if ( ( v.AutoUse && v.CanUse( onlyFriendly, target ) ) &&
                             ( this.Profile.GetOptionValue<bool>( "PRIORITY_CASTING" ) != true || attackSkillUsed == false || ( v.Type != SkillType.Damage && v.Type != SkillType.DoT ) ) )
                        {
                            // break if just checking buff, moving && reached WP. So it can turn
                            if ( onlyFriendly && Bot.Instancia.CurrentWaypointList.Count > 0 && this.Moving )
                            {
                                var curWp = Bot.Instancia.CurrentWaypointList.Current;
                                var distToWp = DistanceToWaypoint( curWp );// Helpers.Distance( this.X, this.Z, curWp.X, curWp.Z );
                                if ( distToWp <= Successdist )
                                { // wp reached
                                    break;
                                }
                                if ( Math.Abs( lastDistToWp - 0 ) > float.Epsilon && distToWp > lastDistToWp )
                                { // moving away from wp
                                    break;
                                }

                                lastDistToWp = distToWp;
                            }

                            // Short time break target: after x casts without damaging
                            target = new Pawn( this.TargetPtr );
                            var takingTooLong = TakingTooLongToDamageTarget( onlyFriendly, target );
                            if ( takingTooLong || ( !onlyFriendly && !target.Alive ) )
                            {
                                if ( Bot.DebugSkills )
                                {
                                    Bot.Debug( "Breaking CheckSkills," + ( ( target.Alive && takingTooLong ) ? " taking too long to damage the target." : "Target is dead." ) );
                                }
                                break;
                            }

                            if ( this.Profile.GetOptionValue<bool>( "PRIORITY_CASTING" ) && ( v.Type == SkillType.Damage || v.Type == SkillType.DoT ) )
                            {
                                attackSkillUsed = true;
                            }
                            if ( v.CastTime > 0 )
                            {
                                Keyboard.ReleaseKey( Profile.HotKeys.MOVE_FORWARD.Key );
                                // Wait to stop only if not an instant cast spell
                                this.WaitTillStopMoving();
                            }

                            var resu = this.CastImpl( v );
                            var success = ( resu is bool && ( bool ) resu );
                            var shouldbreak = false;
                            if ( !success )
                            {
                                if ( Bot.DebugSkills )
                                {
                                    Bot.Debug( "First fail to cast, gonna check messages." );
                                }
                                if ( resu is string && ( string ) resu == "failed to cast" )
                                {
                                    CheckSystemMessages( tryToFaceTarget );
                                    var tryAgain = this.ShouldTryToCastAgain( onlyFriendly, v, target );
                                    while ( tryAgain && !success )
                                    {
                                        if ( !onlyFriendly && ( !target.Alive || !target.Exists ) )
                                        {
                                            used = false;
                                            shouldbreak = true;
                                            break;
                                        }

                                        resu = this.CastImpl( v );
                                        success = ( resu is bool && ( bool ) resu );
                                        Thread.Sleep( 50 );
                                        if ( success )
                                        {
                                            used = true;
                                            this.WaitTillCastingEnds();
                                            break;
                                        }
                                        if ( resu is string && ( string ) resu == "failed to cast" )
                                        {
                                            tryAgain = this.ShouldTryToCastAgain( onlyFriendly, v, target );
                                            if ( !tryAgain )
                                            {
                                                shouldbreak = true;
                                                break;
                                            }
                                        }
                                        else
                                        {
                                            break;
                                        }
                                    }
                                }
                            }
                            else
                            {
                                this.WaitTillCastingEnds();
                                used = true;
                            }
                            if ( shouldbreak )
                            {
                                break;
                            }
                        }
                        if ( !onlyFriendly )
                        {
                            if ( !target.Alive )
                            {
                                break;
                            }
                        }
                    }
                    finally
                    {
                        if ( used )
                        {
                            this.LastSkill = v;
                        }

                        //this.Skills[ i ] = v;
                        Monitor.Exit( v );
                    } // additional potion check while working at a 'casting round'
                }
                this.CheckPotions();
            }
            else
            {
                this.CheckPotions();
            }

            return used;
        }

        private int CheckSystemMessages( int tryToFaceTarget )
        {
            var lastTime = Helpers.GetLastWarning( this.FaceTargetMsg, 2 );
            if ( lastTime > 0 && Math.Abs( lastTime - this.checkskillsLastFacetarget ) > 0.01 )
            {
                this.checkskillsLastFacetarget = lastTime;
                this.FaceTarget();
                if ( tryToFaceTarget > 0 )
                {
                    tryToFaceTarget = 0;
                    Keyboard.HoldKey( Profile.HotKeys.MOVE_BACKWARD.Key );
                    Thread.CurrentThread.Join( 500 );
                    Keyboard.ReleaseKey( Profile.HotKeys.MOVE_BACKWARD.Key );
                    Bot.Debug( string.Format( Resources.MovedBackBecause, this.FaceTargetMsg ) );
                }
                tryToFaceTarget++;
            }
            return tryToFaceTarget;
        }

        private bool TakingTooLongToDamageTarget( bool onlyFriendly, Pawn tgt )
        {
            if ( !onlyFriendly && tgt != null && tgt.Exists ) { }
            if ( tgt == null || !tgt.Exists )
            {
                return false;
            }

            var maxSkillUseNoDmg = this.Profile.GetOptionValue<int>( "MAX_SKILLUSE_NODMG" );
            if ( this.CastToTarget > maxSkillUseNoDmg && ( tgt.LastDamage <= 0 || this.FailedCastInARow > maxSkillUseNoDmg ) && !this.Casting )
            {
                this.AddMobToIgnoreList( tgt.Address );
                this.ClearTarget();
                if ( this.Battling )
                {
                    Thread.CurrentThread.Join( 1000 );
                    Keyboard.HoldKey( Profile.HotKeys.MOVE_BACKWARD.Key );
                    Thread.CurrentThread.Join( 1000 );
                    Keyboard.ReleaseKey( Profile.HotKeys.MOVE_BACKWARD.Key );
                    Thread.CurrentThread.Join( 1000 );
                }

                this.BreakFight = true;
                return true;
            }
            return false;
        }

        public void LoadPath( string file )
        {
            Bot.Instancia.WaypointList.Load( file );
            this.Returning = false;
        }

        /// <summary>
        ///     Esto hace la rotación dependiendo de si se hace con QUICK_TURN o con teclado.
        /// </summary>
        /// <param name="angle"></param>
        /// <param name="yAngle"></param>
        /// <param name="diff"></param>
        private void Rotate( double angle, double? yAngle = null, int diff = 15 )
        {
            if ( null != yAngle )
            {
                if ( !this.Profile.GetOptionValue<bool>( "QUICK_TURN" ) )
                {
                    this.FaceDirection( this.Direction, yAngle );
                }
                else
                {
                    if ( this.Fighting && !this.Profile.GetOptionValue<bool>( "ENABLE_FIGHT_SLOW_TURN" ) )
                    {
                        this.FaceDirection( angle, yAngle );
                        return;
                    }
                }
            }

            var angleDif = Helpers.AngleDifference( angle, this.Direction );
            var rotateStartTime = DateTime.Now;

            if ( this.Profile.GetOptionValue<bool>( "QUICK_TURN" ) || ( this.Fighting && !this.Profile.GetOptionValue<bool>( "ENABLE_FIGHT_SLOW_TURN" ) ) )
            {
                this.FaceDirection( angle );
                return;
            }

            while ( angleDif > Helpers.Rad( diff ) )
            {
                if ( this.HP < 1 || this.Alive == false )
                {
                    return;
                }

                if ( ( DateTime.Now - rotateStartTime ).Seconds > 3.0 )
                {
                    // Sometimes both left and right rotate get stuck down.
                    // Press them both to make sure they are fully released.
                    Keyboard.KeyPress( Profile.HotKeys.ROTATE_LEFT.Key );
                    Keyboard.KeyPress( Profile.HotKeys.ROTATE_RIGHT.Key );

                    // we seem to have been distracted, take a step back.
                    Keyboard.KeyPress( Profile.HotKeys.MOVE_BACKWARD.Key );
                    rotateStartTime = DateTime.Now;
                }

                if ( Helpers.AngleDifference( angle, this.Direction + 0.01 ) < angleDif )
                {
                    // rotate left
                    Keyboard.ReleaseKey( Profile.HotKeys.ROTATE_RIGHT.Key );
                    Keyboard.HoldKey( Profile.HotKeys.ROTATE_LEFT.Key );

                    //this.faceDirection( angle );
                }
                else
                {
                    // rotate right
                    Keyboard.ReleaseKey( Profile.HotKeys.ROTATE_LEFT.Key );
                    Keyboard.HoldKey( Profile.HotKeys.ROTATE_RIGHT.Key );
                }
                //this.faceDirection( angle );

                Thread.CurrentThread.Join( 50 );
                angleDif = Helpers.AngleDifference( angle, this.Direction );
            }

            Keyboard.ReleaseKey( Profile.HotKeys.ROTATE_RIGHT.Key );
            Keyboard.ReleaseKey( Profile.HotKeys.ROTATE_LEFT.Key );
        }

        public bool TargetNpc( object npcNameOrId )
        {
            if ( null == npcNameOrId )
            {
                return false;
            }
            var npc = this.FindNearestNameOrId( npcNameOrId );
            if ( npc == null )
            {
                Bot.Error( string.Format( Resources.NpcNotFound, npcNameOrId ) );
                return false;
            }

            // we successfully found NPC
            Bot.Printf( Color.Green, Resources.NpcSuccesfullyTargeted, npc.Name );
            if ( Helpers.Distance( this.X, this.Z, npc.X, npc.Z ) > 39 )
            {
                this.MoveInRange( new Waypoint( npc.X, npc.Z ), 39, true );
            }
            // target NPC
            this.SetTarget( npc.Address );
            this.Attack();
            Thread.CurrentThread.Join( 50 );
            this.Attack(); // 'click' again to be sure
            Thread.CurrentThread.Join( 500 );
            return true;
        }

        public bool TargetObject( object _objname, int _waittime = 0, bool _harvestall = false, bool _donotignore = false, Func<int, GameObject, bool> evalFunc = null )
        {
            if ( _objname == null )
            {
                return false;
            }
            var minWaitTime = 1000;
            ReleaseKeys();
            this.WaitTillStopMoving();
            var objFound = false;
            GameObject obj = null;
            var interrupted = false;
            while ( true )
            {
                do
                {
                    obj = this.FindNearestNameOrId( _objname, !_donotignore ? this.LastTargetPtr : 0, evalFunc );

                    if ( obj != null && Helpers.Distance( obj, this ) > this.Profile.GetOptionValue<int>( "HARVEST_DISTANCE" ) )
                    {
                        obj = null;
                    }

                    if ( obj == null )
                    {
                        continue;
                    }
                    if ( this.LastTargetPtr != obj.Address )
                    {
                        Bot.Printf( Color.Yellow, Resources.WeFoundAndWillHarvest, obj.Name );
                        this.LastTargetPtr = obj.Address;
                    }

                    objFound = true;
                    this.SetTarget( obj.Address );
                    if ( this.DistanceToTarget > 39 )
                    {
                        this.MoveInRange( obj, 39, true );
                    }
                    Helpers.Yrest( 100 );
                    this.Attack();
                    var timeStart = DateTime.Now;
                    while ( ( DateTime.Now - timeStart ).TotalMilliseconds < minWaitTime )
                    {
                        if ( this.Battling || this.Casting )
                        {
                            break;
                        }
                    }

                    if ( !this.Casting && _waittime > 0 )
                    {
                        this.Attack();
                        Helpers.Yrest( 50 );
                        timeStart = DateTime.Now;
                    }

                    do
                    {
                        Helpers.Yrest( 100 );
                        while ( this.Battling )
                        {
                            if ( this.SetTarget( this.FindEnemy( true, 0, this.EvalTargetDefault ) ) )
                            {
                                this.Fight();
                                interrupted = true;
                            }
                            else
                            {
                                break;
                            }
                        }
                    } while ( this.Running && !interrupted && ( ( DateTime.Now - timeStart ).TotalMilliseconds < _waittime || this.Casting ) );
                } while ( interrupted );
                if ( obj != null )
                {
                    if ( !_donotignore )
                    {
                        this.LastTargetPtr = obj.Address;
                    }
                    if ( !_harvestall )
                    {
                        return objFound;
                    }
                }
                else
                {
                    return objFound;
                }
            }
        }

        public void CraftItem( string itemName = null, int itemGuid = 0, int craftNumber = 0, bool craftAll = true )
        {
            if ( itemName == null && itemGuid == 0 )
            {
                Bot.Warning( "You need to specify item name or GUID." );
                return;
            }

            if ( craftNumber == 0 && !craftAll )
            {
                Bot.Warning( "You need to specify how many you want to craft or craftAll = true." );
                return;
            }

            var item = this.FindItemToCraft( itemName, itemGuid );
            while ( item != null )
            {
                var toCraft = craftNumber;
                if ( craftAll )
                {
                    toCraft = item.MaxCraft;
                }
                var goal = toCraft;
                var last = 0;
                do
                {
                    goal -= last;
                    last = 0;
                    Bot.Printf( Color.Yellow, "Crafting " + goal + " " + item.Name );

                    var c = 0;
                    do
                    {
                        c++;
                        Helpers.RoMScript( "CreateCraftItem(" + item.Guid + "," + goal + ",0)" );
                        Thread.CurrentThread.Join( 200 );
                    } while ( c < 4 && !this.Casting );

                    if ( !this.Casting )
                    {
                        Bot.Error( "Failed to craft. Make sure you are near the correct crafting tools." );
                        return;
                    }

                    do
                    {
                        var count = Memory.ReadRepeatPtr<int>( CraftingCountPtr, CraftingCountOffset );
                        if ( count > 0 )
                        {
                            if ( last != count )
                            {
                                Bot.UpdateProgressBar( count, goal, "Crafting: " + item.Name );
                                //Bot.Printf( Color.YellowGreen, "Crafting {0} - {1} of {2}", item.Name, count, goal );
                            }
                            last = count;
                        }
                        Helpers.Yrest( 500 );
                    } while ( this.Casting );

                    if ( last < goal )
                    {
                        Bot.Print( "Crafting interrupted, will try again to complete crafting." );
                        last--;
                        Helpers.Yrest( 1500 );
                    }
                } while ( last < goal );
                if ( !craftAll )
                {
                    break;
                }

                item = this.FindItemToCraft( itemName, itemGuid );
            }
            Bot.UpdateProgressBar( 0, 0, null, true );
            Bot.Print( "Crafting ended.", Color.GreenYellow );
        }

        public CraftItem FindItemToCraft( string name = null, int guid = 0 )
        {
            if ( name == null && guid == 0 )
            {
                Bot.Warning( "You need to specify item name or GUID." );
                return null;
            }
            if ( guid > 0 )
            {
                var resu = Helpers.RoMScript( "GetCraftItemInfo(" + guid + ")" ) as List<object>;
                if ( resu != null )
                {
                    return new CraftItem { Name = resu[ 0 ] as string, Guid = guid, MaxCraft = ( int ) resu[ 2 ] };
                }
            }
            else
            {
                var count = ( int ) Helpers.RoMScript( "GetCraftItemList()" );
                for ( var typeNo = 1; typeNo <= count; typeNo++ )
                {
                    var resu = Helpers.RoMScript( "} " +
                                                  "f=GetCraftItem " +
                                                  "for C=1,f(" + typeNo + ",1,-1) do " +
                                                  "_,_,_,G=f(" + typeNo + ",1,C) " +
                                                  "N,_,M=GetCraftItemInfo(G) " +
                                                  "if M>0 and string.find(N,\"" + name + "\") then " +
                                                  "a={N,G,M} " +
                                                  "break " +
                                                  "end " +
                                                  "end " +
                                                  "z={" ) as List<object>;
                    if ( resu != null )
                    {
                        return new CraftItem { Name = resu[ 0 ] as string, Guid = ( int ) resu[ 1 ], MaxCraft = ( int ) resu[ 2 ] };
                    }
                }
            }

            return null;
        }

        public void Unstick()
        {
            // After 2x unsuccesfull unsticks try to reach last waypoint
            if ( this.UnstickCounter == 2 )
            {
                if ( this.Unstick3 != null )
                {
                    this.Unstick3();
                    return;
                }
                if ( this.Returning )
                {
                    Bot.Instancia.ReturnWaypointList.Reverse();
                    //We move to the next because, on Reverse, the current remains the same...
                    Bot.Instancia.ReturnWaypointList.MoveNext();
                }
                else
                {
                    Bot.Instancia.WaypointList.Reverse();
                    //We move to the next because, on Reverse, the current remains the same...
                    Bot.Instancia.WaypointList.MoveNext();
                }
                return;
            }

            // after 5x unsuccesfull unsticks try to reach next waypoint after sticky one
            if ( this.UnstickCounter == 6 )
            {
                if ( this.Unstick6 != null )
                {
                    this.Unstick6();
                    return;
                }
                if ( this.Returning )
                {
                    Bot.Instancia.ReturnWaypointList.MoveNext();
                    //Bot.Instancia.ReturnWaypointList.MoveNext();
                }
                else
                {
                    Bot.Instancia.WaypointList.MoveNext();
                    //Bot.Instancia.WaypointList.MoveNext();
                }
                return;
            }

            // after 8x unsuccesfull unsticks try to reach next waypoint after sticky one
            if ( this.UnstickCounter == 9 )
            {
                if ( this.Unstick9 != null )
                {
                    this.Unstick9();
                    return;
                }
                //turn and move back for 10 seconds
                Keyboard.HoldKey( Profile.HotKeys.ROTATE_RIGHT.Key );
                Thread.CurrentThread.Join( 1900 );
                Keyboard.ReleaseKey( Profile.HotKeys.ROTATE_RIGHT.Key );
                Keyboard.HoldKey( Profile.HotKeys.MOVE_FORWARD.Key );
                Thread.CurrentThread.Join( 10000 );
                Keyboard.ReleaseKey( Profile.HotKeys.MOVE_FORWARD.Key );

                var currWp = this.Returning ? Bot.Instancia.ReturnWaypointList.Current : Bot.Instancia.WaypointList.Current;
                if ( this.Returning )
                {
                    Bot.Instancia.ReturnWaypointList.Current = Bot.Instancia.ReturnWaypointList.GetNearestWaypoint( this.X, this.Z );
                    if ( Bot.Instance.ReturnWaypointList.Current == currWp )
                    {
                        //Advance to the next if the one jammed is still the closer one
                        Bot.Instance.ReturnWaypointList.MoveNext();
                    }
                }
                else
                {
                    Bot.Instancia.WaypointList.Current = Bot.Instancia.WaypointList.GetNearestWaypoint( this.X, this.Z );
                    if ( Bot.Instance.WaypointList.Current == currWp )
                    {
                        //Advance to the next if the one jammed is still the closer one
                        Bot.Instance.WaypointList.MoveNext();
                    }
                }
                return;
            }

            if ( this.UnstickCounter >= 10 )
            {
                Bot.AssistMode = true;
                return;
            }

            // Move back for x seconds
            Keyboard.HoldKey( Profile.HotKeys.MOVE_BACKWARD.Key );
            Thread.CurrentThread.Join( 1000 );
            Keyboard.ReleaseKey( Profile.HotKeys.MOVE_BACKWARD.Key );

            //Straff either left or right now
            var straffkey = Key.VK_NONE;
            var rnd = new Random( DateTime.Now.Millisecond );
            if ( rnd.Next( 100 ) < 50 )
            {
                straffkey = Profile.HotKeys.STRAFF_LEFT.Key;
            }
            else
            {
                straffkey = Profile.HotKeys.STRAFF_RIGHT.Key;
            }

            var straffBonus = this.UnstickCounter * 120;
            Keyboard.HoldKey( straffkey );
            Thread.CurrentThread.Join( 500 + rnd.Next( 500 ) + straffBonus );
            Keyboard.ReleaseKey( straffkey );

            // try to jump over a obstacle
            if ( this.UnstickCounter > 1 )
            {
                if ( this.UnstickCounter == 2 )
                {
                    Keyboard.HoldKey( Profile.HotKeys.MOVE_FORWARD.Key );
                    Thread.CurrentThread.Join( 550 );
                    Keyboard.KeyPress( Profile.HotKeys.JUMP.Key );
                    Thread.CurrentThread.Join( 400 );
                    Keyboard.ReleaseKey( Profile.HotKeys.MOVE_FORWARD.Key );
                }
                else if ( rnd.Next( 100 ) < 80 )
                {
                    Keyboard.HoldKey( Profile.HotKeys.MOVE_FORWARD.Key );
                    Thread.CurrentThread.Join( 600 );
                    Keyboard.KeyPress( Profile.HotKeys.JUMP.Key );
                    Thread.CurrentThread.Join( 400 );
                    Keyboard.ReleaseKey( Profile.HotKeys.MOVE_FORWARD.Key );
                }
            }
        }

        public void AddMobToIgnoreList( Pawn pawn )
        {
            if ( pawn == null || pawn.Address == 0 )
            {
                return;
            }
            AddMobToIgnoreList( pawn.Address );
        }

        public void AddMobToIgnoreList( int address )
        {
            this.LastPlaceMobIgnored = new Waypoint( this.X, this.Z, this.Y );
            this.MobIgnoreList.Add( new IgnoredMob { Address = address, Time = DateTime.Now } );
        }

        public void ClearMobIgnoreList()
        {
            // Only clear list if you have traveled 50 from last ignore
            if ( this.LastPlaceMobIgnored == null || Helpers.Distance( this, this.LastPlaceMobIgnored ) > 50 )
            {
                this.MobIgnoreList.Clear();
            }
        }

        public void Attack()
        {
            if ( Profile.HotKeys.AttackType.Key == Key.VK_NONE )
            {
                Macros.SetupAttackKey();
            }

            var tmpTargetPtr = Memory.ReadRepeat<uint>( Bot.Instancia.ProcId, this.Address + Addresses.pawnTargetPtr_offset );

            if ( tmpTargetPtr == 0 && this.targetPtr == 0 )
            {
                return;
            }

            if ( tmpTargetPtr != 0 )
            {
                this.targetPtr = ( int ) tmpTargetPtr;
                if ( Profile.HotKeys.AttackType.Key == Key.MACRO )
                {
                    Helpers.RoMScript( "UseSkill(1,1)" );
                }
                else
                {
                    Keyboard.KeyPress( Profile.HotKeys.AttackType.Key );
                }
            }

            if ( this.targetPtr != 0 )
            {
                // freeze TargetPtr
                //Memory.WriteString( Addresses.functionTargetPatchAddr, "".PadLeft( Addresses.functionTargetBytes.Length - 1, ( char ) 0x90 ) );
                Memory.WriteBytes( Addresses.functionTargetPatchAddr, Addresses.functionTargetBytes.Select( c => ( byte ) 0x90 ).ToArray(), Addresses.functionTargetBytes.Length );

                // Target it
                Memory.WriteInt( ( uint ) this.Address + Addresses.pawnTargetPtr_offset, this.targetPtr );

                if ( Profile.HotKeys.AttackType.Key == Key.MACRO )
                {
                    Helpers.RoMScript( "UseSkill(1,1)" );
                }
                else
                {
                    Keyboard.KeyPress( Profile.HotKeys.AttackType.Key );
                }

                Thread.CurrentThread.Join( 100 );

                //Unfreeze targetPtr
                var bytes = Addresses.functionTargetBytes.Select( Convert.ToByte ).ToArray();
                Memory.WriteBytes( Addresses.functionTargetPatchAddr, bytes, Addresses.functionTargetBytes.Length );
                //Memory.WriteString( Addresses.functionTargetPatchAddr, s );
            }
            //UpdateTargetPtr();
        }

        public bool SetTarget( object pawnOrAddress )
        {
            if ( pawnOrAddress == null )
            {
                return false;
            }
            var address = 0;
            if ( pawnOrAddress is int )
            {
                address = ( int ) pawnOrAddress;
            }
            else if ( pawnOrAddress is GameObject )
            {
                address = ( ( GameObject ) pawnOrAddress ).Address;
            }

            if ( address == 0 )
            {
                return false;
            }

            var addressId = Memory.ReadRepeat<uint>( Bot.Instancia.ProcId, address + Addresses.pawnId_offset );

            if ( addressId == 0 || addressId > 999999 ) // The pawn or object no longer exists
            {
                this.TargetPtr = 0;
                return false;
            }

            var flags = Memory.ReadUInt( address + Addresses.pawnAttackable_offset );
            if ( Helpers.BitAnd( flags, 0x10 ) ) // Has bloodbar
            {
                var guid = Memory.ReadUInt( address + Addresses.pawnGUID_offset );
                Helpers.RoMScript( "OBB_ChangeTraget(" + guid + ")" );
            }
            //Memory.WriteInt( ( uint ) this.Address + Addresses.pawnTargetPtr_offset, address );
            this.TargetPtr = address;
            this.Target = new Pawn( this.targetPtr );
            this.LastTargetPtr = address;
            return true;
        }

        private object CastImpl( Skill skill )
        {
            var prior = Helpers.SkillUsePrior;
            var lastGlobalcooldown = 0d;

            if ( skill.Cooldown > 0 && skill.RemainingCooldown > 1 )
            {
                return false;
            }
            var hfTemp = Key.VK_NONE;
            if ( skill.HotKey == Key.MACRO || skill.HotKey == Key.VK_NONE )
            {
                hfTemp = Key.MACRO;
            }
            //else
            //    hfTemp = getKeyName(skill.hotkey);

            if ( !this.OnPreSkillCastActive && this.Profile.HasOnBeforeSkillCast )
            {
                this.OnPreSkillCastActive = true;
                var resu = this.Profile.OnBeforeSkillCast( skill );
                this.OnPreSkillCastActive = false;
                if ( resu is bool && !( bool ) resu )
                {
                    return false;
                }
            }

            this.WaitTillCastingEnds( skill, prior );
            // break if target is dead
            if ( ( skill.Type == SkillType.Damage || skill.Type == SkillType.DoT ) &&
                 !this.HasTarget && skill.AoECenter != AoECenter.Player )
            {
                return false;
            }

            // fixes instant cast after timed cast timing problem, waits till finished
            while ( Math.Abs( skill.CastTime - 0 ) < 0.01 && this.Casting )
            {
                Thread.CurrentThread.Join( 10 );
            }

            this.FaceTarget();

            lastGlobalcooldown = this.GlobalCooldown;
            this.UpdateMP();
            double oldMana = this.Mana, oldRage = this.Rage, oldEnergy = this.Energy, oldFocus = this.Focus;
            var actualDamage = this.Target != null ? this.Target.LastDamage : 0d;
            var skillUsed = skill.Use();
            // count attempted cast to enemy targets
            if ( skill.Target is Pawn && ( skill.Type == SkillType.Damage || skill.Type == SkillType.DoT ) )
            { // target is unfriendly
                this.CastToTarget++;
            }

            // Bypass the rest as Warden Summon is alread completed
            if ( this.Class1 == PawnClass.Warden && skill.Type == SkillType.Summon )
            {
                return skillUsed;
            }
            // first part of 'casting +.'
            this.WaitTillPriorEnds( skill, prior, lastGlobalcooldown );

            // Wait for casting or GCD to start
            if ( skillUsed && this.WaitTillCastingStarts( skill ) == false ) // failed to cast/aborted
            {
                return "failed to cast";
            }

            if ( skill.Toggleable )
            {
                skill.Toggled = true;
            }

            var powerUsed = true;
            // Wait until energy use in memory updates. Only for instants
            if ( Math.Abs( skill.CastTime - 0 ) < 0.01 && ( skill.GlobalCooldown || skill.UsesPower ) )
            {
                powerUsed = false;
                var mpstart = DateTime.Now;
                while ( ( DateTime.Now - mpstart ).TotalMilliseconds < 700 )
                {
                    Thread.CurrentThread.Join( 10 );
                    this.UpdateMP();
                    if ( skill.Mana > 0 )
                    {
                        if ( this.Mana < oldMana )
                        {
                            powerUsed = true;
                            break;
                        }
                    }
                    else if ( skill.Rage > 0 )
                    {
                        if ( this.Rage < oldRage )
                        {
                            powerUsed = true;
                            break;
                        }
                    }
                    else if ( skill.Energy > 0 )
                    {
                        if ( this.Energy < oldEnergy )
                        {
                            powerUsed = true;
                            break;
                        }
                    }
                    else if ( skill.Focus > 0 )
                    {
                        if ( this.Focus < oldFocus )
                        {
                            powerUsed = true;
                            break;
                        }
                    }
                    else
                    {
                        break;
                    }
                }
            }

            if ( skillUsed )
            {
                if ( !this.OnSkillCastActive && this.Profile.HasOnSkillCast )
                {
                    this.OnSkillCastActive = true;
                    this.Profile.OnSkillCast( skill );
                    this.OnSkillCastActive = false;
                }

                if ( skill.Type == SkillType.Damage || skill.Type == SkillType.DoT || skill.Type == SkillType.Debuff )
                {
                    var target = new Pawn( this.TargetPtr );
                    Thread.CurrentThread.Join( 100 );
                    var ld = target.LastDamage;
                    if ( target.Exists && ( Casting || ( ld > 0 || skill.Type == SkillType.Debuff ) ) || powerUsed )
                    {
                        Bot.Printf( "Used {0}: {1}{2}", hfTemp, skill.Name, ( ( skill.Type == SkillType.Damage || skill.Type == SkillType.DoT || skill.Type == SkillType.Debuff ) ) ? string.Format( " on {0} {1}/{2}", target.Name, target.MaxHP, target.HP ) : "" );
                    }
                    else if ( ld <= 0 && ( skill.Type == SkillType.Damage || skill.Type == SkillType.DoT ) )
                    {
                        this.FailedCastInARow++;
                        Bot.Printf( "Failed to use {0}: {1}", hfTemp, skill.Name );
                        skillUsed = false;
                    }
                }
                else
                {
                    Bot.Printf( "Used {0}: {1}", hfTemp, skill.Name );
                }
            }
            return skillUsed;
        }

        private bool WaitTillCastingStarts( Skill skill )
        {
            var startTime = DateTime.Now;
            if ( skill.CastTime > 0 )
            {
                while ( this.Running && !this.Casting )
                { // wait for casting to start
                    // break cast with jump if aggro before casting finished
                    if ( this.CheckAggroBeforeCast( true, skill.Type ) )
                    { // with jump
                        Bot.Print( Resources.GotAggroCastingAborted );
                        return false;
                    }

                    Thread.CurrentThread.Join( 50 );
                    if ( ( DateTime.Now - startTime ).TotalMilliseconds > 1500 )
                    { // Assume failed to caste after 1.5 sec
                        //printf(language[180]);	// close print 'Casting +." / aborted
                        if ( skill.Type == SkillType.Damage || skill.Type == SkillType.DoT )
                        {
                            this.FailedCastInARow++;
                        }
                        return false;
                    }
                }
            }
            else if ( skill.Cooldown > 0 )
            {
                while ( this.Running && skill.RemainingCooldown <= 0 )
                {
                    if ( !skill.Toggleable )
                    {
                        if ( skill.HotKey == Key.MACRO || skill.HotKey == Key.VK_NONE )
                        {
                            Helpers.RoMScript( "CastSpellByName(\"" + MemoryTable.GetIdName( skill.Id ) + "\")" );
                            //SlashCommand("/script CastSpellByName(\""+MemoryTable.GetIdName(skill.Id)+"\");");
                        }
                        else
                        {
                            Keyboard.KeyPress( skill.HotKey, skill.Modifier );
                            //keyboardPress(skill.hotkey, skill.modifier);
                        }
                    }
                    if ( this.RestWhileCheckingForWaypoint( 300 ) == false )
                    {
                        break;
                    } // break to head to new wp
                    if ( ( DateTime.Now - startTime ).TotalMilliseconds > 1500 )
                    { // Assume failed to caste after .7 sec
                        //printf(language[180]);	// close print 'Casting +." / aborted
                        if ( skill.Type == SkillType.Damage || skill.Type == SkillType.DoT )
                        {
                            this.FailedCastInARow++;
                        }
                        return false;
                    }
                }
                var remaining = skill.RemainingCooldown;
                if ( remaining > 0 )
                {
                    skill.LastCastTime = DateTime.Now;
                    skill.LastCastTime = skill.LastCastTime.AddMilliseconds( remaining * 1000 * Bot.GetTimeFrequency );
                }
            }
            else if ( skill.GlobalCooldown )
            { // Wait for global cooldown to start
                //Bot.Printf( Color.Red, "GlobalCoolDown: {0}", GlobalCooldown );
                while ( this.Running && this.GlobalCooldown == 0 )
                { // wait for casting to start
                    if ( this.RestWhileCheckingForWaypoint( 50 ) == false )
                    {
                        break;
                    } // break to head to new wp
                    //Bot.Printf( Color.Red, "GlobalCoolDown (dentro): {0}", GlobalCooldown );
                    if ( ( DateTime.Now - startTime ).TotalMilliseconds > ( 1000 - Helpers.Ping ) )
                    { // Assume failed to caste after .7 sec
                        //printf(language[180]);	// close print 'Casting +." / aborted
                        if ( skill.Type == SkillType.Damage || skill.Type == SkillType.DoT )
                        {
                            this.FailedCastInARow++;
                        }
                        return false;
                    }
                }
            }
            skill.LastCastTime = DateTime.Now;
            skill.LastCastTime = skill.LastCastTime.AddMilliseconds( this.RemainingCastTime.CastTime * 1000 * Bot.GetTimeFrequency );
            if ( skill.Type == SkillType.Damage || skill.Type == SkillType.DoT )
            {
                this.FailedCastInARow = 0;
            }

            return true;
        }

        private void WaitTillPriorEnds( Skill skill, double prior, double lastGlobalcooldown )
        {
            if ( this.Casting && this.RemainingCastTime.Left <= prior / 1000 )
            {
                do
                {
                    Thread.CurrentThread.Join( 10 );
                } while ( this.Casting && this.RemainingCastTime.Left > prior / 1000 );
            }
            else if ( skill.Cooldown > 0 && skill.RemainingCooldown <= prior / 1000 )
            {
                while ( skill.RemainingCooldown > 0 && skill.RemainingCooldown > prior / 1000 )
                {
                    Thread.CurrentThread.Join( 10 );
                }
            }
            else if ( this.GlobalCooldown > 0 && this.GlobalCooldown < lastGlobalcooldown )
            {
                // Wait until global cooldown is 0 || jumps to the next skill
                do
                {
                    Thread.CurrentThread.Join( 10 );
                } while ( this.GlobalCooldown > 0 || this.GlobalCooldown > lastGlobalcooldown );
            }
        }

        private void WaitTillCastingEnds( Skill skill, double prior )
        {
            while ( this.Running && this.Casting )
            {
                if ( this.LastSkill == null )
                {
                    Thread.CurrentThread.Join( 10 );
                    continue;
                }
                // break cast with jump if aggro before casting finished
                if ( this.CheckAggroBeforeCast( true, this.LastSkill.Type ) )
                { //  with jump
                    //printf(language[82]);	// close print 'Casting +."
                    Bot.Print( Resources.GotAggroCastingAborted );
                    return;
                }

                // break if target is dead
                if ( ( this.LastSkill.Type == SkillType.Damage ||
                       this.LastSkill.Type == SkillType.DoT ) &&
                     !this.HasTarget )
                {
                    return;
                }

                // Waiting for casting to finish+.
                Thread.CurrentThread.Join( 10 );

                this.FaceTarget();

                // leave before Casting flag is gone, so we can cast faster, but only if skill doesn't trigger global cooldown && ClickToCast != true
                if ( this.LastSkill.GlobalCooldown != true && skill.ClickToCast != true )
                {
                    if ( this.RemainingCastTime.Left <= prior / 1000 )
                    {
                        // end of waiting early
                        break;
                    }
                }
            }
            if ( skill.Cooldown > 0 )
            {
                while ( this.Running && skill.RemainingCooldown > prior / 1000 )
                {
                    // Waiting for cooldown to finish+.
                    Thread.CurrentThread.Join( 10 );
                }
            }
            if ( this.LastSkill != null )
            {
                if ( this.LastSkill.CastTime > 0 ||
                     ( this.LastSkill.Mana == 0 && this.LastSkill.Rage == 0 && this.LastSkill.Energy == 0 && this.LastSkill.Focus == 0 ) )
                {
                    while ( this.Running && this.GlobalCooldown > prior / 1000 )
                    {
                        // Waiting for global cooldown to finish+.
                        Thread.CurrentThread.Join( 10 );
                    }
                }
            }
        }

        public object Cast( object castSkill )
        {
            var skill = castSkill is string ? this.Skills.FirstOrDefault( s => s.ID == ( string ) castSkill ) : ( Skill ) castSkill;
            if ( skill == null )
            {
                return false;
            }

            return this.CastImpl( skill );
        }

        private bool CheckAggroBeforeCast( bool jump, SkillType skillType )
        {
            // break cast in last moment
            // works also if target is not visible && we get aggro from another mob
            // _jump = true       abort cast with jump hotkey

            var target = new Pawn( this.TargetPtr );

            // don't break if no target || self targeting
            if ( !target.Exists ||
                 this.TargetPtr == this.Address )
            {
                return false;
            }

            if ( this.Battling == false )
            { // no aggro
                return false;
            }

            // don't break friendly skills
            if ( skillType != SkillType.Damage &&
                 skillType != SkillType.DoT )
            {
                return false;
            }

            // Don't break if we already started damaging target
            if ( target.LastDamage > 0 )
            {
                return false;
            }

            // Don't break if target is targeting us
            if ( target.TargetPtr == this.Address ||
                 target.TargetIsFriend() )
            {
                return false;
            }

            // Don't break if cast is nearly finished as it will probably still cast.
            if ( this.Casting && this.RemainingCastTime.Left <= Helpers.SkillUsePrior )
            {
                return false;
            }

            // target is alive and not attacking us
            if ( this.CastToTarget == 0 )
            {
                Bot.Printf( Color.Green, Resources.AggroDuringFirstStrikeCastAbortThatCastTarget, target.Name );
                //cprintf(cli.green, language[36], target.Name);	// Aggro during first strike/cast
            }

            // try fo find the aggressore a little faster by targeting it itself instead of waiting from the client
            target = this.FindEnemy( true, 0, this.EvalTargetDefault );
            if ( target != null && target.Address != this.TargetPtr )
            { // we found a new target
                this.SetTarget( target );
                Bot.Printf( Color.Green, "{0} is attacking us, we take that target.", target.Name );
            }
            else
            {
                return false;
            }

            return true;
        }

        public void Merchant( object npcNameOrId, int option = 1 )
        {
            if ( OpenStore( npcNameOrId, option ) )
            {
                Store.Update();
                Bot.Printf( Color.Tomato, "Store: {0}, ItemsCount: {1}, CanRepair: {2}", Store.Name, Store.Items.Count, Store.CanRepair );

                Store.BuyConsumable( ConsumableType.HoT, Profile.GetOptionValue<int>( "HEALING_POTION" ) );
                Store.BuyConsumable( ConsumableType.MoT, Profile.GetOptionValue<int>( "MANA_POTION" ) );
                Store.BuyConsumable( ConsumableType.ArrowQuiver, Profile.GetOptionValue<int>( "ARROW_QUIVER" ) );
                Store.BuyConsumable( ConsumableType.ThrownBag, Profile.GetOptionValue<int>( "THROWN_BAG" ) );
                Store.BuyConsumable( ConsumableType.Poison, Profile.GetOptionValue<int>( "POISON" ) );

                Inventory.AutoSell();
            }

            Helpers.RoMScript( "CloseWindows();" );
        }

        public void Mount( bool dismount = false )
        {
            if ( this.Mounted && !dismount )
            {
                return;
            }

            Helpers.RoMScript( "PartnerFrame_CallPartner(2,1)" );
            Thread.CurrentThread.Join( 500 + Helpers.Ping );
            do
            {
                Thread.CurrentThread.Join( 100 );
            } while ( this.Casting );
        }

        public void Dismount()
        {
            Mount( true );
        }


        /// <summary>
        /// Switchs classes at house maid.
        /// </summary>
        /// <param name="npcNameOrid">Name or id of the house maid, ex: Jeddar mays</param>
        /// <param name="changeClassNameOrid">This is the option number or name to "I want to switch my primary class..."</param>
        /// <param name="to">usually Player.Class2.</param>
        /// <param name="from">Usually Player.Class1</param>
        public void SwitchClass( object npcNameOrid, object changeClassNameOrid, PawnClass to, PawnClass from )
        {
            if ( null == npcNameOrid || null == changeClassNameOrid )
            {
                Bot.Error( "You have to specify both options, npcNameOrId and changeClassNameOrid" );
                return;
            }
            if ( TargetNpc( npcNameOrid ) )
            {
                if ( changeClassNameOrid is string )
                {
                    if ( Helpers.ChoiceOptionByName( ( string ) changeClassNameOrid ) )
                    {
                        Helpers.SwitchClass( from, to );
                    }
                }
                else
                {
                    Helpers.RoMScript( "ChoiceOption(" + changeClassNameOrid + ");" );
                    Helpers.Yrest( 250 + Helpers.Ping );
                    Helpers.SwitchClass( from, to );
                }
            }
        }

        public bool EvalTargetDefault( int address, Pawn target )
        {
            // Can't have self as target
            if ( address == this.Address )
            {
                if ( Bot.DebugFight )
                {
                    Bot.Debug( "address == this.Address" );
                }
                return false;
            }

            if ( target == null && address != 0 )
            {
                target = new Pawn( address );
            }

            if ( target == null && this.Target != null )
            {
                target = this.Target;
                //target.Update();
            }

            if ( target == null )
            {
                return false;
            }

            // Check if still valid target
            if ( !target.Exists )
            {
                if ( Bot.DebugFight )
                {
                    Bot.Debug( "target is no longer valid" );
                }
                return false;
            }

            // Not attackable
            if ( !target.Attackable )
            {
                if ( Bot.DebugFight )
                {
                    Bot.Printf( "target is not Attackable {0}, AttackableFlag: {1}", target.Name, target.AttackableFlag );
                }
                return false;
            }

            if ( !target.Alive )
            {
                if ( Bot.DebugFight )
                {
                    Bot.Printf( "target is not Alive {0}", target.Name );
                }
                return false;
            }

            if ( target.IsPet )
            {
                if ( Bot.DebugFight )
                {
                    Bot.Printf( "target is pet {0}", target.Name );
                }
                return false;
            }

            //== Check aggro ==//
            ////////////////////-

            if ( this.Battling )
            { // Battling flag is on
                if ( target.TargetPtr == this.Address || // We are being targeted
                     target.TargetIsFriend( true ) ) // Or friend is being targeted
                {
                    if ( target.Type != ObjectType.Player || this.Profile.GetOptionValue<bool>( "PVP" ) )
                    { //  Check PVP
                        return true;
                    }
                }
            }

            //== Non aggro checks ==//
            //////////////////////////

            // don't target NPCs
            if ( target.Type == ObjectType.NpcOrNode )
            { // NPCs are type == 4

                if ( Bot.DebugFight )
                {
                    Bot.Printf( "thats a NPC, he should be friendly and not attackable. {0}", target.Name );
                }
                return false; // he is not a valid target
            }

            // check level of target against our leveldif settings
            if ( ( target.Level - this.Level ) > this.Profile.GetOptionValue<int>( "TARGET_LEVELDIF_ABOVE" ) ||
                 ( this.Level - target.Level ) > this.Profile.GetOptionValue<int>( "TARGET_LEVELDIF_BELOW" ) )
            {
                if ( Bot.DebugFight )
                {
                    Bot.Printf( "target lvl ({0}) above/below profile settings without battling. {1}", target.Level, target.Name );
                }
                return false; // he is not a valid target
            }

            // check if on the ignore list
            if ( target.IsOnMobIgnoreList() )
            {
                //target.UpdateName();
                //cprintf(cli.green, language[87], target.Name);
                if ( Bot.DebugFight )
                {
                    Bot.Printf( "Mob is on ignore list {0}", target.Name );
                }
                return false;
            }

            // Dead
            if ( target.HP < 1 )
            {
                if ( Bot.DebugFight )
                {
                    Bot.Debug( "target HP is less than 1" );
                }
                return false;
            }

            // Also dead (and has loot)
            if ( target.Lootable )
            {
                if ( Bot.DebugFight )
                {
                    Bot.Debug( "target is lootable therefore dead" );
                }
                return false;
            }

            // Check height difference
            if ( Math.Abs( target.Y - this.Y ) > 45 )
            {
                if ( Bot.DebugFight )
                {
                    Bot.Debug( "target height difference is too great" );
                }
                return false;
            }

            // check distance to target against MAX_TARGET_DIST
            var maxTargetDist = this.Profile.GetOptionValue<int>( "MAX_TARGET_DISTANCE" );
            if ( this.DistanceTo( target ) > maxTargetDist )
            {
                if ( Bot.DebugFight )
                {
                    Bot.Debug( "target dist > MAX_TARGET_DIST to player" );
                }
                return false; // he is not a valid target
            }

            if ( !Bot.AssistMode )
            {
                //Check against distance to wp
                var wpA = Bot.Instance.CurrentWaypointList.Current;
                var wpB = Bot.Instance.CurrentWaypointList.LastWaypoint;
                var v = Helpers.GetNearestSegmentPoint( this.X, this.Z, wpA.X, wpA.Z, wpB.X, wpB.Z );
                if ( target.X > ( v.X - maxTargetDist ) &&
                     target.X < ( v.X + maxTargetDist ) &&
                     target.Z > ( v.Z - maxTargetDist ) &&
                     target.Z < ( v.Z + maxTargetDist ) )
                {
                    if ( Helpers.Distance( v.X, v.Z, target.X, target.Z ) > maxTargetDist )
                    {
                        if ( Bot.DebugFight )
                        {
                            Bot.Debug( "target dist > MAX_TARGET_DIST to Waypoint" );
                        }
                        return false; // he is not a valid target
                    }
                    ;
                }
                else
                {
                    // must be too far away
                    if ( Bot.DebugFight )
                    {
                        Bot.Debug( "target dist > MAX_TARGET_DIST to waypoint" );
                    }
                    return false;
                }
            }

            // Mob limitations defined?
            if ( this.Profile.Mobs.Count > 0 && !this.IsInMobs( target ) )
            {
                if ( Bot.DebugFight )
                {
                    Bot.Printf( "Mob limitation is set, mob {0} is not a valid target.", target.Name );
                }
                return false; // he is not a valid target
            }

            // target is to strong for us
            if ( !this.Profile.GetOptionValue<bool>( "PARTY_INSTANCE" ) && this.Profile.GetOptionValue<int>( "AUTO_ELITE_FACTOR" ) > 0 )
            {
                if ( target.MaxHP > this.MaxHP * this.Profile.GetOptionValue<int>( "AUTO_ELITE_FACTOR" ) )
                {
                    if ( Bot.DebugFight )
                    {
                        Bot.Printf( "Target {0} is to strong. More HP then this.MaxHP * settings.profile.options.AUTO_ELITE_FACTOR", target.Name );
                    }
                    return false;
                }
            }

            if ( this.Profile.GetOptionValue<bool>( "ANTI_KS" ) )
            {
                if ( target.TargetPtr != this.Address && !target.TargetIsFriend() )
                {
                    // If the target's TargetPtr is 0,
                    // that doesn't necessarily mean they don't
                    // have a target (game bug, not a bug in the bot)
                    if ( target.TargetPtr == 0 )
                    {
                        if ( target.HP < target.MaxHP )
                        {
                            if ( Bot.DebugFight )
                            {
                                Bot.Debug( "anti kill steal: target not fighting us: unknown target" );
                            }
                            return false;
                        }
                    }
                    else
                    {
                        var targettarget = new Pawn( target.TargetPtr );
                        if ( targettarget.Type == ObjectType.Player && !targettarget.IsFriend() )
                        {
                            // They definitely target another player.
                            // If it is a friend, we can help.
                            // Otherwise, leave it alone.
                            if ( Bot.DebugFight )
                            {
                                Bot.Debug( "anti kill steal: target not fighting us: target isn't targeting a friend" );
                            }
                            return false;
                        }
                    }
                }
            }

            return true;
        }

        public bool IsInMobs( Pawn pawn )
        {
            return this.Profile.Mobs.Count > 0 && this.Profile.Mobs.Any( m => pawn.Name.ToLower().Contains( m.ToLower() ) || pawn.Name.ToLower().Equals( m.ToLower(), StringComparison.OrdinalIgnoreCase ) );
        }

        public void ResetSkillLastCastTime()
        {
            Monitor.Enter( this.Skills );
            foreach ( var skill in this.Skills.Where( s => s.ID != "PRIEST_SOUL_BOUND" ) ) /* PRIEST_SOUL_BOUND has a cooldown of 1800 seconds that doesnt get resetted on death. */
            {
                skill.LastCastTime = DateTime.Now;
            }
            Monitor.Exit( this.Skills );
        }

        /// <summary>
        ///     This will feed a pet, can be your assist pet, your craft pet or any pet you have.
        /// </summary>
        /// <param name="eggSlot">The slot for the pet to feed.</param>
        /// <param name="foodNameOrId">The food you want to feed the pet with, can be name or id.</param>
        /// <param name="count">The amount of food you want to give to your pet, pass "all" to to use all of that food.</param>
        public void FeedPet( int eggSlot, object foodNameOrId, object count )
        {
            if ( this.AssistPet != null && this.AssistPet.EggSlot == eggSlot )
            {
                this.AssistPet.Feed( foodNameOrId, count );
            }
            else if ( this.CraftPet != null && this.CraftPet.EggSlot == eggSlot )
            {
                this.CraftPet.Feed( foodNameOrId, count );
            }
            else
            {
                var pet = new EggPet( eggSlot );
                if ( !string.IsNullOrEmpty( pet.Name ) )
                {
                    pet.Feed( foodNameOrId, count );
                }
            }
        }

        public PlayerUserData UserData
        {
            get { return userData; }
        }

        public void RestRnd( int probability, int restmin, int restmax )
        {
            // call the rest function with a given probability
            if ( new Random().Next( 100 ) < probability )
            {
                this.Rest( restmin, restmax );
            }
        }

        public void Rest( int restmin = 0, int restmax = 0, string resttype = "time", int restaddrnd = 0 )
        {
            if ( this.Battling )
            {
                return;
            }

            Keyboard.ReleaseKey( Profile.HotKeys.MOVE_FORWARD.Key );
            if ( restmin == 0 )
            {
                restmin = 10;
            }
            if ( restmax == 0 )
            {
                restmax = restmin;
            }
            if ( string.IsNullOrEmpty( resttype ) )
            {
                resttype = "time"; //default restype is 'time"
            }

            var hfResttime = restmin;
            if ( restmax > restmin )
            {
                hfResttime = restmin + new Random().Next( restmax - restmin );
            }

            if ( restaddrnd != 0 )
            {
                restaddrnd = new Random().Next( restaddrnd );
            }

            this.UpdateMP();

            var hfManaRest = ( this.MaxMana * this.Profile.GetOptionValue<int>( "MP_REST" ) / 100 ); // rest if mana is lower then
            var hfHPRest = ( this.MaxHP * this.Profile.GetOptionValue<int>( "HP_REST" ) / 100 ); // rest if HP is lower then

            var restStart = DateTime.Now; // set start timer

            this.CheckPotions();
            this.CheckSkills( true );

            // sit option is false as default && the option is not promoted
            // simply because if you misconfigure the rest option / don't use potions you will
            // rest && sit after every fight && that looks really bottish
            var sitWhileResting = ( this.Profile.GetOptionValue<string>( "SIT_WHILE_RESTING" ) ?? "" ).ToLower();
            if ( resttype == "full" && // only sit for full rest, not for times restings
                 ( sitWhileResting == "true" || sitWhileResting == "random" ) )
            {
                if ( sitWhileResting == "true" )
                {
                    Helpers.RoMScript( "SitOrStand()" );
                }
                else
                {
                    if ( new Random().Next( 10 ) >= 5 )
                    {
                        Helpers.RoMScript( "SitOrStand()" );
                    }
                }
            }

            Bot.Printf( Color.Yellow, "About to rest for: {0} seconds.", hfResttime );
            var restingWatch = Stopwatch.StartNew();
            while ( this.Running && restingWatch.Elapsed.TotalSeconds < hfResttime )
            {
                if ( this.Battling )
                {
                    this.ClearTarget();
                    break;
                }
                Bot.UpdateProgressBar( ( int ) ( hfResttime - restingWatch.Elapsed.TotalSeconds ), hfResttime, "Resting..." );
                //if ( restingWatch.Elapsed.TotalSeconds >= hfResttime )
                //{
                //    break;
                //}

                // check if HP/Mana full
                this.UpdateMP();
                if ( this.Mana == this.MaxMana && // some chars have MaxMana = 0
                     this.HP == this.MaxHP &&
                     resttype == "full" )
                { // Mana && HP are full
                    var restAddStart = DateTime.Now; // set additional rest timer
                    while ( true )
                    { // rnd addition
                        if ( ( DateTime.Now - restAddStart ).TotalSeconds > restaddrnd )
                        {
                            break;
                        }
                        if ( this.Battling )
                        { // we get aggro,
                            this.ClearTarget(); // get rid of mob to be able to target attackers
                            Bot.Print( "We got aggro, interrupting rest." );
                            //cprintf(cli.green, language[39] );   // Stop resting because of aggro
                            break;
                        }
                        this.CheckPotions();
                        this.CheckSkills( true ); // only cast friendly spells to ourselfe
                        Thread.CurrentThread.Join( 100 );
                    }

                    //cprintf(cli.green, language[70], (DateTime.Now - restStart ) );   // full after x sec
                    Bot.Printf( Color.Green, "We are full after {0} seconds, terminating rest.", restingWatch.Elapsed.TotalSeconds );
                    break;
                }

                if ( this.Stance != 7 )
                { // can't cast while sitting
                    this.CheckPotions();
                    this.CheckSkills( true ); // only cast friendly spells to ourselfe
                }

                Thread.CurrentThread.Join( 100 );
            }
            Bot.ClearProgressBar();
            if ( this.Stance == 7 && !this.Battling ) //if aggro the char will standup automaticly
            {
                Helpers.RoMScript( "SitOrStand()" );
                Thread.CurrentThread.Join( 1500 ); //Give time to stand up
            }
        }

        public void WaitTillStopMoving( int maxtime = 0 )
        {
            if ( !this.Moving )
            {
                return;
            }
            maxtime = maxtime > 0 ? maxtime : 400;
            var starttime = DateTime.Now;
            do
            {
                Thread.CurrentThread.Join( 10 );
            } while ( this.ActualSpeed > 0 && ( DateTime.Now - starttime ).TotalMilliseconds < maxtime );
        }

        public void SetSpeed( float speed )
        {
            Memory.WriteFloat( this.Address + 0x40, speed );
        }

        public void ResetSpeed()
        {
            this.SetSpeed( 50f );
        }

        /// <summary>
        ///     Disable only the skills marked with AutoUse = true
        /// </summary>
        public void DisableSkills()
        {
            this.disabledSkills.Clear();
            foreach ( var skill in this.Skills.Where( s => s.AutoUse ) )
            {
                this.disabledSkills.Add( skill.Id );
                skill.AutoUse = false;
            }
        }

        public void EatFood( object foodnameOrId, object buffNameOrid = null )
        {
            if ( foodnameOrId == null )
            {
                return;
            }
            var item = Inventory.FindItem( foodnameOrId );
            if ( item == null || item.Name == "<UNKNOWN>" )
            {
                Bot.Printf( Color.Yellow, "foodnameOrId: {0} not found.", foodnameOrId );
                return;
            }
            if ( item.ItemCount == 0 )
            {
                Bot.Printf( Color.Yellow, "You don't have {0}.", item.Name );
                return;
            }
            if ( buffNameOrid != null )
            {
                var buff = GetBuff( buffNameOrid.ToString() );
                if ( buff == null )
                {
                    item.Use();
                }
                else
                {
                    Bot.Printf( Color.Yellow, "You already have the buff {0}.", buff.Name );
                }
            }
            else //We asume that "food" will give the same buff as its name
            {
                var buff = GetBuff( item.Name );
                if ( buff == null )
                {
                    item.Use();
                }
                else
                {
                    Bot.Printf( Color.Yellow, "You already have the buff {0}.", buff.Name );
                }
            }
        }

        /// <summary>
        ///     Re enable player skills, but only those tha were automatically disabled by DisableSkills
        /// </summary>
        public void EnableSkills()
        {
            foreach ( var skill in this.Skills.Where( s => this.disabledSkills.Contains( s.Id ) ) )
            {
                skill.AutoUse = true;
            }
            this.disabledSkills.Clear();
        }

        /// <summary>
        ///     Set all skills in profile to AutoUse = true
        /// </summary>
        public void EnableAllSkills()
        {
            this.disabledSkills.Clear();
            foreach ( var skill in this.Skills )
            {
                skill.AutoUse = true;
            }
        }

        public void Pause()
        {
            this.Stopped = false;
            this.Paused = true;
            ReleaseKeys();
        }

        public void Resume()
        {
            this.Paused = false;
            this.ResetAllTasks();
        }

        public void Stop()
        {
            this.Stopped = true;
            this.Paused = true;
            ReleaseKeys();
        }

        public void Start()
        {
            this.Stopped = false;
            this.Paused = false;
            this.ResetAllTasks();
        }

        internal static void ReleaseKeys()
        {
            //Keyboard.ReleaseKey( Profile.HotKeys.ROTATE_LEFT.Key );
            //Keyboard.ReleaseKey( Profile.HotKeys.ROTATE_RIGHT.Key );

            //Keyboard.ReleaseKey( Profile.HotKeys.MOVE_FORWARD.Key );
            //Keyboard.ReleaseKey( Profile.HotKeys.MOVE_BACKWARD.Key );

            Memory.WriteBytePtr( Addresses.staticbase_char, Addresses.moveKeysPressed_offset, 0 );
        }

        public bool OpenStore( object npcname, int option = 1 )
        {
            if ( this.TargetNpc( npcname ) )
            {
                Thread.CurrentThread.Join( 1000 );
                Helpers.RoMScript( "ChoiceOption(" + option + ")" );
                Thread.CurrentThread.Join( 1000 + Helpers.Ping );
                return Helpers.RoMScript<bool>( "StoreFrame:IsVisible()" );
            }
            return false;
        }

        public void UseTeleport( object npcNameOrid, object option )
        {
            if ( npcNameOrid == null || option == null )
            {
                Bot.Error( string.Format( "Bad name or option passed to UseTeleport - npcNameOrId: {0}, option: {1}", npcNameOrid, option ) );
                return;
            }
            if ( TargetNpc( npcNameOrid ) )
            {
                var optionFound = false;
                if ( option is int )
                {
                    var resu = Helpers.RoMScript( "GetSpeakOption(" + option + ")" ) as List<object>;
                    var optionName = resu != null ? ( string ) resu[ 0 ] : null;
                    Helpers.Yrest( 50 );
                    Helpers.RoMScript( "ChoiceOption(" + option + ");" );
                    Helpers.Yrest( 50 + Helpers.Ping );
                    optionFound = !( resu == null || ( string ) resu[ 0 ] != optionName );
                }
                else
                {
                    var strOption = option.ToString();
                    if ( string.IsNullOrEmpty( strOption ) )
                    {
                        Bot.Error( string.Format( "Bad option passed to UseTeleport - option: {0}", option ) );
                        return;
                    }
                    if ( strOption.Contains( "|" ) )// It means its more than one step to use the teleporter (more than one page), can be option numbers or names.
                    {
                        var vals = strOption.Split( '|' );
                        foreach ( var val in vals )
                        {
                            int tmpInt;
                            if ( int.TryParse( val, out tmpInt ) )
                            {
                                var resu = Helpers.RoMScript( "GetSpeakOption(" + tmpInt + ")" ) as List<object>;
                                var optionName = resu != null ? ( string ) resu[ 0 ] : null;
                                Helpers.Yrest( 50 );
                                Helpers.RoMScript( "ChoiceOption(" + tmpInt + ");" );
                                Helpers.Yrest( 50 + Helpers.Ping );
                                resu = Helpers.RoMScript( "GetSpeakOption(" + val + ")" ) as List<object>;
                                optionFound = !( resu == null || ( string ) resu[ 0 ] != optionName );
                            }
                            else
                            {
                                optionFound = Helpers.ChoiceOptionByName( val );
                            }
                            Helpers.Yrest( 250 + Helpers.Ping );
                        }
                    }
                    else
                    {
                        optionFound = Helpers.ChoiceOptionByName( strOption );
                    }
                }
                if ( optionFound )
                {
                    Helpers.Yrest( 200 + Helpers.Ping );
                    Helpers.RoMScript( "StaticPopup_EnterPressed(StaticPopup1);StaticPopup1:Hide();" );
                    Helpers.WaitForLoadingScreen( 5 );
                }
                else
                {
                    Bot.Printf( Color.Tomato, "Seems like your option is not found on that NPC's dialog... {0}", option );
                }
            }
        }

        public void Logout()
        {
            //TODO: proper logout and shutdown
            // Close all windows to avoid problems when relogging.
            Helpers.RoMScript( "CloseAllWindows()" );
            Helpers.Yrest( 500 );

            Helpers.RoMScript( "Logout();" );
            Helpers.Yrest( 30000 ); // Wait for the log out to process

        }
    }

    public class IgnoredMob
    {
        public int Address { get; set; }
        public DateTime Time { get; set; }
    }

    public struct CastingTime
    {
        public float CastTime;
        public float Left;

        public CastingTime( float time, float left )
        {
            this.CastTime = time;
            this.Left = left;
        }
    }

    public class KilledMob
    {
        public string Name { get; set; }
        public int Exp { get; set; }
        public int Tp { get; set; }
        public TimeSpan Time { get; set; }
        public int Level { get; set; }

        public override string ToString()
        {
            return string.Format( "{0} (lvl {1}), Exp: {2}, Tp: {3}, Time: {4}", this.Name, this.Level, this.Exp, this.Tp, this.Time );
        }
    }

    public class LootSigil
    {
        public GameObject Sigil { get; set; }
        public float Dist { get; set; }

        public override string ToString()
        {
            return string.Format( "[SIGIL] Name: {0}, Dist: {1}", this.Sigil.Name, this.Dist );
        }
    }

    public class CraftItem
    {
        public int Guid;
        public int MaxCraft;
        public string Name;

        public override string ToString()
        {
            return string.Format( "Name: {0}, GUID: {1}, MaxCraft: {2}", this.Name, this.Guid, this.MaxCraft );
        }
    }
}